This is one of a series of patches that will merge the layout modules development in personal/branches/rgheck back into the tree.

Design goal: Allow the use of layout "modules", which are to LaTeX packages as layout files are to LaTeX document classes. Thus, one could have a module that defined certain character styles, environments, commands, or what have you, and include it in various documents, each of which uses a different document class, without having to modify the layout files themselves. For example, a theorems.module could be used with article.layout to provide support for theorem-type environments, without having to modify article.layout itself, and the same module could be used with book.layout, etc.

This second patch adds the backend. The ModuleList class holds a list of the available modules, which are retrieved from lyxmodules.lst, itself generated by configure.py. There are two LFUNs available: modules-clear and module-add, which do the obvious thing; you can test by typing these into the minibuffer, along with the name of one of the available modules: URL (a CharStyle), Endnote (a Custom Inset), and---with the spaces---End To Foot (View>LaTeX and look at the user preamble), which are themselves in lib/layouts. The GUI will come next. A theorems module will follow, when I get a chance. Other ideas more than welcome!

Issues: (i) The configure.py script could be improved. It'd be nice, for example, if it tested for the presence of the LaTeX packages a particular module needs. But this would mean re-working the LaTeX script, and I don't know how to do that. Note that at present, the packages are ignored. This will change shortly. (ii) I've used std::string in LyXModule, following what seemed to be a precedent in TextClass. If some of these should be docstrings, please let me know, and I'll change them. (iii) There is at present no distinction between LaTeX and DocBook modules. Should there be? That is: Should there be modules that are available when the document class is a LaTeX class and others that are available only when it is DocBook? Or should there just be one modules?

Richard

--
==================================================================
Richard G Heck, Jr
Professor of Philosophy
Brown University
http://frege.brown.edu/heck/
==================================================================
Get my public key from http://sks.keyserver.penguin.de
Hash: 0x1DE91F1E66FFBDEC
Learn how to sign your email using Thunderbird and GnuPG at:
http://dudu.dyn.2-h.org/nist/gpg-enigmail-howto

Index: src/TextClass.cpp
===================================================================
--- src/TextClass.cpp	(revision 19769)
+++ src/TextClass.cpp	(working copy)
@@ -107,6 +107,7 @@
 	  floatlist_(new FloatList), counters_(new Counters),
 	  texClassAvail_(texClassAvail)
 {
+	modular_ = false;
 	outputType_ = LATEX;
 	columns_ = 1;
 	sides_ = OneSide;
@@ -173,7 +174,7 @@
 
 
 // Reads a textclass structure from file.
-bool TextClass::read(FileName const & filename, bool merge)
+bool TextClass::read(FileName const & filename, ReadType rt)
 {
 	if (!support::isFileReadable(filename)) {
 		lyxerr << "Cannot read layout file `" << filename << "'."
@@ -208,14 +209,21 @@
 		{ "tocdepth",        TC_TOCDEPTH }
 	};
 
-	if (!merge)
-		LYXERR(Debug::TCLASS) << "Reading textclass "
-				      << to_utf8(makeDisplayPath(filename.absFilename()))
-				      << endl;
-	else
-		LYXERR(Debug::TCLASS) << "Reading input file "
-				      << to_utf8(makeDisplayPath(filename.absFilename()))
-				      << endl;
+	switch (rt) {
+	case BASECLASS:
+		LYXERR(Debug::TCLASS) << "Reading textclass ";
+		break;
+	case MERGE:
+		LYXERR(Debug::TCLASS) << "Reading input file ";
+	  break;
+	case MODULE:
+		LYXERR(Debug::TCLASS) << "Reading module file ";
+		break;
+	default:
+		BOOST_ASSERT(false);
+	}
+	LYXERR(Debug::TCLASS) << to_utf8(makeDisplayPath(filename.absFilename()))
+		<< endl;
 
 	Lexer lexrc(textClassTags,
 		sizeof(textClassTags) / sizeof(textClassTags[0]));
@@ -264,7 +272,7 @@
 					lexrc.printError("Could not find input"
 							 "file: " + inc);
 					error = true;
-				} else if (read(tmp, true)) {
+				} else if (read(tmp, MERGE)) {
 					lexrc.printError("Error reading input"
 							 "file: " + tmp.absFilename());
 					error = true;
@@ -441,12 +449,20 @@
 		FileName const tempfile(support::tempName());
 		error = !layout2layout(filename, tempfile);
 		if (!error)
-			error = read(tempfile, merge);
+			error = read(tempfile, rt);
 		support::unlink(tempfile);
 		return error;
 	}
 
-	if (!merge) { // we are at top level here.
+	if (rt == MODULE) 
+		LYXERR(Debug::TCLASS) << "Finished reading module file "
+				<< to_utf8(makeDisplayPath(filename.absFilename()))
+				<< endl;
+	else if (rt == MERGE)
+		LYXERR(Debug::TCLASS) << "Finished reading input file "
+				<< to_utf8(makeDisplayPath(filename.absFilename()))
+				<< endl;
+	else { // we are at top level here.
 		LYXERR(Debug::TCLASS) << "Finished reading textclass "
 				      << to_utf8(makeDisplayPath(filename.absFilename()))
 				      << endl;
@@ -476,10 +492,7 @@
 			<< "Minimum TocLevel is " << min_toclevel_
 			<< ", maximum is " << max_toclevel_ <<endl;
 
-	} else
-		LYXERR(Debug::TCLASS) << "Finished reading input file "
-				      << to_utf8(makeDisplayPath(filename.absFilename()))
-				      << endl;
+	}
 
 	return error;
 }
Index: src/LyXAction.cpp
===================================================================
--- src/LyXAction.cpp	(revision 19769)
+++ src/LyXAction.cpp	(working copy)
@@ -372,6 +372,8 @@
 		{ LFUN_CLEARPAGE_INSERT, "clearpage-insert", Noop },
 		{ LFUN_CLEARDOUBLEPAGE_INSERT, "cleardoublepage-insert", Noop },
 		{ LFUN_LISTING_INSERT, "listing-insert", Noop },
+		{ LFUN_MODULES_CLEAR, "modules-clear", Noop },
+		{ LFUN_MODULE_ADD, "module-add", Noop },
 
 		{ LFUN_NOACTION, "", Noop }
 	};
Index: src/insets/InsetInclude.cpp
===================================================================
--- src/insets/InsetInclude.cpp	(revision 19769)
+++ src/insets/InsetInclude.cpp	(working copy)
@@ -493,6 +493,26 @@
 			Alert::warning(_("Different textclasses"), text);
 			//return 0;
 		}
+		
+		// Make sure modules used in child are all included in master
+		//FIXME It might be worth loading the children's modules into the master
+		//over in BufferParams rather than doing this check.
+		vector<string> const masterModules = m_buffer->params().getModules();
+		vector<string> const childModules = tmp->params().getModules();
+		vector<string>::const_iterator it = childModules.begin();
+		vector<string>::const_iterator end = childModules.end();
+		for (; it != end; ++it) {
+			string const module = *it;
+			vector<string>::const_iterator found = 
+				find(masterModules.begin(), masterModules.end(), module);
+			if (found != masterModules.end()) {
+				docstring text = bformat(_("Included file `%1$s'\n"
+							"uses module `%2$s'\n"
+							"which is not used in parent file."),
+			 	       makeDisplayPath(included_file.absFilename()), from_utf8(module));
+				Alert::warning(_("Module not found"), text);
+			}
+		}
 
 		tmp->markDepClean(m_buffer->temppath());
 
Index: src/LyX.cpp
===================================================================
--- src/LyX.cpp	(revision 19769)
+++ src/LyX.cpp	(working copy)
@@ -36,6 +36,7 @@
 #include "LyXFunc.h"
 #include "Lexer.h"
 #include "LyXRC.h"
+#include "ModuleList.h"
 #include "Server.h"
 #include "ServerSocket.h"
 #include "TextClassList.h"
@@ -925,6 +926,8 @@
 	LYXERR(Debug::INIT) << "Reading layouts..." << endl;
 	if (!LyXSetStyle())
 		return false;
+	//...and the modules
+	moduleList.load();
 
 	if (use_gui) {
 		// Set the language defined by the user.
@@ -1114,6 +1117,7 @@
 		first_start = false;
 
 		return needsUpdate("lyxrc.defaults")
+			|| needsUpdate("lyxmodules.lst")
 			|| needsUpdate("textclass.lst")
 			|| needsUpdate("packages.lst");
 	}
Index: src/TextClass.h
===================================================================
--- src/TextClass.h	(revision 19769)
+++ src/TextClass.h	(working copy)
@@ -61,8 +61,8 @@
 	TextClass(std::string const & = std::string(),
 		     std::string const & = std::string(),
 		     std::string const & = std::string(),
-		     bool = false);
-
+		     bool texClassAvail = false);
+	
 	/// check whether the TeX class is available
 	bool isTeXClassAvailable() const;
 
@@ -71,8 +71,9 @@
 	/// paragraph styles end iterator
 	const_iterator end() const { return layoutlist_.end(); }
 
+	enum ReadType { BASECLASS, MERGE, MODULE };
 	/// Performs the read of the layout file.
-	bool read(support::FileName const & filename, bool merge = false);
+	bool read(support::FileName const & filename, ReadType rt = BASECLASS);
 	///
 	void readOutputType(Lexer &);
 	///
@@ -125,6 +126,13 @@
 	///
 	std::string const & description() const;
 	///
+	bool isModular() { return modular_; };
+	///
+	bool isModular() const { return modular_; }
+	/// Sets the layout as a modular one. There is never any
+	/// need to reset this.
+	void markAsModular() { modular_ = true; }
+	///
 	std::string const & opt_fontsize() const;
 	///
 	std::string const & opt_pagestyle() const;
@@ -192,11 +200,14 @@
 	std::string latexname_;
 	/// document class description
 	std::string description_;
-	/// Specific class options
+	/// whether this is a modular layout, i.e., whether it has been
+	/// modified by loading of layout modules.
+	bool modular_;
+	///
 	std::string opt_fontsize_;
 	///
 	std::string opt_pagestyle_;
-	///
+	/// Specific class options
 	std::string options_;
 	///
 	std::string pagestyle_;
Index: src/BufferParams.h
===================================================================
--- src/BufferParams.h	(revision 19769)
+++ src/BufferParams.h	(working copy)
@@ -122,6 +122,14 @@
 	/// Should be called with care and would be better not being here,
 	/// but it seems to be needed by CutAndPaste::putClipboard().
 	void setTextClass(TextClass_ptr);
+	/// List of modules in use
+	std::vector<std::string> const & getModules() const;
+	/// Add a module to the list of modules in use.
+	bool addLayoutModule(std::string modName, bool makeClass = true);
+	/// Add a list of modules
+	bool addLayoutModules(std::vector<std::string>modNames);
+	/// Clear the list
+	void clearLayoutModules();
 
 	/// returns the main font for the buffer (document)
 	Font const getFont() const;
@@ -291,6 +299,9 @@
 	void readBullets(Lexer &);
 	///
 	void readBulletsLaTeX(Lexer &);
+	///
+	void readModules(Lexer &);
+	/// Adds the module information to the baseClass information to
 	/// create our local TextClass.
 	void makeTextClass();
 
@@ -301,6 +312,10 @@
 	textclass_type baseClass_;
 	/// the possibly modular TextClass actually in use
 	TextClass_ptr textClass_;
+	///
+	typedef std::vector<std::string> LayoutModuleList;
+	/// 
+	LayoutModuleList layoutModules_;
 
 	/** Use the Pimpl idiom to hide those member variables that would otherwise
 	 *  drag in other header files.
Index: src/Makefile.am
===================================================================
--- src/Makefile.am	(revision 19769)
+++ src/Makefile.am	(working copy)
@@ -205,6 +205,8 @@
 	Messages.h \
 	MetricsInfo.cpp \
 	MetricsInfo.h \
+	ModuleList.cpp \
+	ModuleList.h \
 	Mover.cpp \
 	Mover.h \
 	output.cpp \
Index: src/LyXFunc.cpp
===================================================================
--- src/LyXFunc.cpp	(revision 19769)
+++ src/LyXFunc.cpp	(working copy)
@@ -729,6 +729,8 @@
 	case LFUN_TEXTCLASS_LOAD:
 	case LFUN_BUFFER_SAVE_AS_DEFAULT:
 	case LFUN_BUFFER_PARAMS_APPLY:
+	case LFUN_MODULES_CLEAR:
+	case LFUN_MODULE_ADD:
 	case LFUN_LYXRC_APPLY:
 	case LFUN_BUFFER_NEXT:
 	case LFUN_BUFFER_PREVIOUS:
@@ -1772,6 +1774,20 @@
 					it->dispatch(cur, fr);
 			break;
 		}
+		
+		case LFUN_MODULES_CLEAR: {
+			BOOST_ASSERT(lyx_view_);
+			lyx_view_->buffer()->params().clearLayoutModules();
+			updateFlags = Update::Force;
+			break;
+		}
+		
+		case LFUN_MODULE_ADD: {
+			BOOST_ASSERT(lyx_view_);
+			lyx_view_->buffer()->params().addLayoutModule(argument);
+			updateFlags = Update::Force;
+			break;
+		}
 
 		case LFUN_TEXTCLASS_APPLY: {
 			BOOST_ASSERT(lyx_view_);
Index: src/lfuns.h
===================================================================
--- src/lfuns.h	(revision 19769)
+++ src/lfuns.h	(working copy)
@@ -400,6 +400,8 @@
 	LFUN_BUFFER_WRITE_ALL,           // rgh, gpothier 200707XX
 	//290
 	LFUN_PARAGRAPH_PARAMS,           // rgh, 200708XX
+	LFUN_MODULES_CLEAR,              // rgh, 20070825
+	LFUN_MODULE_ADD,                 // rgh, 20070825
 
 	LFUN_LASTACTION                  // end of the table
 };
Index: src/Buffer.cpp
===================================================================
--- src/Buffer.cpp	(revision 19769)
+++ src/Buffer.cpp	(working copy)
@@ -440,6 +440,7 @@
 	params().headsep.erase();
 	params().footskip.erase();
 	params().listings_params.clear();
+	params().clearLayoutModules();
 	
 	for (int i = 0; i < 4; ++i) {
 		params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i];
Index: src/BufferParams.cpp
===================================================================
--- src/BufferParams.cpp	(revision 19769)
+++ src/BufferParams.cpp	(working copy)
@@ -27,6 +27,7 @@
 #include "Language.h"
 #include "LaTeXFeatures.h"
 #include "Messages.h"
+#include "ModuleList.h"
 #include "Color.h"
 #include "Font.h"
 #include "Lexer.h"
@@ -41,6 +42,7 @@
 #include "insets/InsetListingsParams.h"
 
 #include "support/convert.h"
+#include "support/filetools.h"
 #include "support/Translator.h"
 
 #include <boost/array.hpp>
@@ -55,7 +57,9 @@
 using std::ostream;
 using std::ostringstream;
 using std::pair;
-
+using std::string;
+using lyx::support::FileName;
+using lyx::support::libFileSearch;
 using lyx::support::bformat;
 using lyx::support::rtrim;
 using lyx::support::tokenPos;
@@ -472,9 +476,13 @@
 						 "for more information.\n"), from_utf8(classname));
 			frontend::Alert::warning(_("Document class not available"),
 				       msg + _("LyX will not be able to produce output."));
-		}
+		} 
+		
 	} else if (token == "\\begin_preamble") {
 		readPreamble(lex);
+	} else if (token == "\\begin_modules") {
+		readModules(lex);
+		makeTextClass();
 	} else if (token == "\\options") {
 		lex.eatLine();
 		options = lex.getString();
@@ -625,6 +633,8 @@
 	} else if (token == "\\float_placement") {
 		lex >> float_placement;
 	} else {
+		lyxerr << "BufferParams::readToken(): Unknown token: " << 
+			token << endl;
 		return token;
 	}
 
@@ -653,6 +663,15 @@
 	if (!options.empty()) {
 		os << "\\options " << options << '\n';
 	}
+	
+	//the modules
+	if (!layoutModules_.empty()) {
+		os << "\\begin_modules" << '\n';
+		LayoutModuleList::const_iterator it = layoutModules_.begin();
+		for (; it != layoutModules_.end(); it++)
+			os << *it << '\n';
+		os << "\\end_modules" << '\n';
+	}
 
 	// then the text parameters
 	if (language != ignore_language)
@@ -1227,9 +1246,69 @@
 void BufferParams::makeTextClass()
 {
 	textClass_.reset(new TextClass(textclasslist[getBaseClass()]));
+	//FIXME It might be worth loading the children's modules here,
+	//instead of just doing a check in InsetInclude.
+	LayoutModuleList::const_iterator it = layoutModules_.begin();
+	for (; it != layoutModules_.end(); it++) {
+		string const modName = *it;
+		LyXModule * lm = moduleList[modName];
+		if (!lm) {
+			docstring const msg =
+						bformat(_("The module %1$s has been requested by\n"
+						"this document but has not been found in the list of\n"
+						"available modules. If you recently installed it, you\n"
+						"probalby need to reconfigure LyX.\n"), from_utf8(modName));
+			frontend::Alert::warning(_("Module not available"),
+															 msg + _("Some layouts may not be available."));
+			lyxerr << "BufferParams::makeTextClass(): Module " <<
+					modName << " requested but not found in module list." <<
+					endl;
+			continue;
+		}
+		FileName layout_file = libFileSearch("layouts", lm->filename);
+		textClass_->read(layout_file, TextClass::MODULE);
+	}
 }
 
 
+std::vector<string> const & BufferParams::getModules() const {
+	return layoutModules_;
+}
+
+
+
+bool BufferParams::addLayoutModule(string modName, bool makeClass) {
+	LayoutModuleList::const_iterator it = layoutModules_.begin();
+	for (; it != layoutModules_.end(); it++) {
+		if (*it == modName) 
+			break;
+	}
+	if (it != layoutModules_.end())
+		return false;
+	layoutModules_.push_back(modName);
+	if (makeClass)
+		makeTextClass();
+	return true;
+}
+
+
+bool BufferParams::addLayoutModules(std::vector<string>modNames)
+{
+	bool retval = true;
+	std::vector<string>::const_iterator it = modNames.begin();
+	for (; it != modNames.end(); ++it)
+		retval = addLayoutModule(*it, false);
+	makeTextClass();
+	return retval;
+}
+
+
+void BufferParams::clearLayoutModules() {
+	layoutModules_.clear();
+	makeTextClass();
+}
+
+
 Font const BufferParams::getFont() const
 {
 	Font f = getTextClass().defaultfont();
@@ -1327,6 +1406,23 @@
 }
 
 
+void BufferParams::readModules(Lexer & lex)
+{
+	if (!lex.eatLine()) {
+		lyxerr << "Error (BufferParams::readModules):"
+				"Unexpected end of input." << endl;
+		return;
+	}
+	while (true) {
+		string mod = lex.getString();
+		if (mod == "\\end_modules")
+			break;
+		addLayoutModule(mod);
+		lex.eatLine();
+	}
+}
+
+
 string const BufferParams::paperSizeName() const
 {
 	char real_papersize = papersize;
Index: lib/configure.py
===================================================================
--- lib/configure.py	(revision 19769)
+++ lib/configure.py	(working copy)
@@ -639,6 +639,9 @@
         # for chkconfig.ltx
         p1 = re.compile(r'\Declare(LaTeX|DocBook)Class')
         testclasses = list()
+#        for file in glob.glob( os.path.join('layouts', '*.mod') ) + \
+#            glob.glob( os.path.join(srcdir, 'layouts', '*.mod' ) ) :
+#          print file
         for file in glob.glob( os.path.join('layouts', '*.layout') ) + \
             glob.glob( os.path.join(srcdir, 'layouts', '*.layout' ) ) :
             if not os.path.isfile(file):
@@ -737,6 +740,60 @@
         ''.join(lyxin))
 
 
+def checkModulesConfig():
+  removeFiles(['lyxmodules.lst'])
+
+  print '+checking list of modules... '
+  tx = open('lyxmodules.lst', 'w')
+  tx.write('''## This file declares modules and their associated definition files.
+## It has been automatically generated by configure
+## Use "Options/Reconfigure" if you need to update it after a
+## configuration change. 
+''')
+  # build the list of available modules
+  foundClasses = []
+  for file in glob.glob( os.path.join('layouts', '*.module') ) + \
+      glob.glob( os.path.join(srcdir, 'layouts', '*.module' ) ) :
+      # valid file?
+      print file
+      if not os.path.isfile(file): 
+          continue
+      tx.write(processModuleFile(file, bool_docbook, bool_linuxdoc))
+  tx.close()
+  print '\tdone'
+
+def processModuleFile(file, bool_docbook, bool_linuxdoc):
+    ''' process module file and get a line of result
+
+        Declare lines look like this:
+          \DeclareLyXModule[LaTeX Packages]{Description}{ModuleName}...
+        We expect output:
+          "ModuleName" "filename" "Description"
+        "
+    '''
+    p = re.compile(r'\DeclareLyXModule\s*(?:\[([^]]*)\])?{(.*)}{(.*)}')
+    for line in open(file).readlines():
+        res = p.search(line)
+        if res != None:
+            (packages, desc, modname) = res.groups()
+            #check availability...need to add that
+            if modname == None:
+              modname = desc
+              desc = packages
+              packages = ""
+            elif packages != None:
+              pkgs = [s.strip() for s in packages.split(",")]
+              packages = ",".join(pkgs)
+
+            filename = file.split(os.sep)[-1]
+            #return '"%s" "%s" "%s" "%s"\n' % (modname, filename, desc, '.'.join(pkgs))
+            return '"%s" "%s" "%s"\n' % (modname, filename, desc)
+    print "Module file without \DeclareLyXModule line. "
+    sys.exit(2)
+
+
+
+
 def checkTeXAllowSpaces():
     ''' Let's check whether spaces are allowed in TeX file names '''
     tex_allows_spaces = 'false'
@@ -830,4 +887,5 @@
     # --without-latex-config can disable lyx_check_config
     checkLatexConfig( lyx_check_config and LATEX != '', bool_docbook, bool_linuxdoc)
     createLaTeXConfig()
+    checkModulesConfig() #lyx_check_config and LATEX != '')
     removeTempFiles()
Index: lib/Makefile.am
===================================================================
--- lib/Makefile.am	(revision 19769)
+++ lib/Makefile.am	(working copy)
@@ -1012,7 +1012,10 @@
 	layouts/agu_stdsections.inc \
 	layouts/agu_stdtitle.inc \
 	layouts/g-brief2.layout \
-	layouts/svglobal.layout
+	layouts/svglobal.layout \
+	layouts/endnotes.module \
+	layouts/endtofoot.module \
+	layouts/url.module
 
 scriptsdir = $(pkgdatadir)/scripts
 # Note that we "chmod 755" manually these files in install-data-hook.

Reply via email to