% jedmodes.sl
% Utilities for the publication of modes at Jedmodes
%
% Keywords: tools
% Authors: Günter Milde, Paul Boekholt
%
% Contains a script for generating a JMR dcdata file from a template, with
% some information extracted from your S-Lang source, a function to upload
% files with scp and  new-mode and new-version wizards.
%
% Copy the dcdata.txt template to Jed_Home.
% You should make a directory structure in your Jed_Home_Directory
% that shadows your JMR directories.
%
% Example illustrating the use with cal.sl: (This is how I, Paul Boekholt
% do it)
%
% Jed-Home
%   |--mode-index.php
%   |
%   `--jedmodes
%      |
%      |--cal/
%      |   |--dcdata.txt
%      |   |--index.php -> ../../mode-index.php
%      |--bufed/
%      |
%      ...
%
% The script creates the cal directory, the dcdata.txt, and the index.php
% symlink.  Simply open cal.sl in JED and type M-x make_dcdata_file.  The
% dcdata.txt file will pop up, edit it as necessary and save.
%
% As an example, here is what the generated dcdata file for this script
% looks like for me:
%
% # See also http://jedmodes.sf.net/doc/DC-Metadata/dcmi-terms-for-jedmodes.txt
% # Compulsory Terms
% title:            jedmodes
% abstract:         Script for creating dcdata
% creator: 	    Boekholt, Paul
% description:
% subject:          tools.slang
% requires: 	    templates
% rights: Copyright (c) 2003 Paul Boekholt
%  Released under the terms of the GNU General Public License (v. 2 or later)
% # Terms with auto-guesses at Jedmodes
% .....
%
% Notes:
%
% - though this mode has two authors, only Paul Boekholt is in the
%   'creator' field.  This is because this field is not guessed from the
%   slang source - it comes from the get_username() function.
%
% - neither is the description or the version
%
% You can use the jedmodes_upload() and jedmodes_new_version()
% functions to upload the mode from within JED.
%
% Or you create the src/mode/cal/ subdirectory and copy cal.sl (or
% cal.tgz) there.  Repeat the process for any other modes you
% wish to create or update.  Then leave JED, chdir to Jed_Home and do
%
% $> tar -czvf modes.tgz `find . -cnewer modes.tgz`
% $> scp modes.tgz boekholt@ssh.sf.net://home/groups/j/je/jedmodes/htdocs/mode
% $> ssh ssh.sf.net
%
% sf> cd /home/groups/j/je/jedmodes/htdocs/mode
% sf> tar -xzvf modes.tgz.
%
% sf> exit
%
% Changes
% -------
%
% 2004-12-16  Some bugfixes in jedmodes_new_mode()
% 2006-05-23  jedmodes_diff: Compare whatbuf() and version in Jedmodes CVS copy
% 2007-01-24  removed  insert_dcdata(), as it is outdated and never used
% 2007-05-14  clean up jedmodes_new_mode(), new fun jedmodes_fetch_dcdata()
% 	      commit_mode(): replace placeholder "2009-02-16 with current date

provide("jedmodes");

% Requirements
% ------------

% modes from % http://jedmodes.sf.net
require("templates");
autoload("get_line", "txtutils");
require("ishell");
require("browse_url");
% maybe some more hidden ones...

% Variables
% ---------

% the directory where you shadow your JMR directories. Has to be
% called "jedmodes" - leave as is.
custom_variable("Jedmodes_Dir", dircat(Jed_Home_Directory, "jedmodes"));

custom_variable("Jedmodes_CVS_Root",
   dircat(Jed_Home_Directory, "jedmodes/src/mode/"));

% the line no where you usually write a summary of what the mode does.
% In this script, it's on line two.
custom_variable("Jedmodes_Abstract_Line_No", 2);

% if you use the dabbrev from jedmodes, you may want to set
public variable Dabbrev_Default_Buflist=1;
% to have dabbrev() expand from all visible buffers

% --- Functions ---

% scan current buffer for requirements (require() and autoload() calls).
define jedmodes_get_requirements()
{
   variable pattern, requirement, requirements = Assoc_Type[Int_Type];
   push_spot_bob();
   foreach pattern (["^require ?(\"\\([^)]+\\)\");",
		     "^[ \t]*autoload ?(\"[^\"]+\" ?, ?\"\\([^)]+\\)\");"])
     {
	while (re_fsearch(pattern))
	  {
	     requirement = path_basename(regexp_nth_match(1));
	     % add requirement, if it is not in the standard library
	     !if (file_status(path_concat(
		path_concat(JED_ROOT, "lib"), requirement + ".sl")))
	       % use assoc to throw out doublettes
	       requirements[requirement] = 1;
	     go_right_1();
	  }
	bob;
     }
   pop_spot();
   return strjoin(assoc_get_keys(requirements), "; ");
}

% simple praragraph formatting logic for dcdata.txt files:
% a line with colon marks the start of a new paragraph
static define dc_parsep()
{
   bol;
   ffind_char(':');
}

% Assuming the current buffer is a Jed mode, try to open its "dcdata.txt" file
public define jedmodes_find_dcdata()
{
   variable mode_dir = path_concat(Jedmodes_Dir, path_sans_extname(whatbuf()));
   !if(file_status(mode_dir))
     mode_dir = path_concat(Jedmodes_Dir,
			    read_mini("Mode Directory",
				      path_sans_extname(whatbuf()), ""));
   read_file(dircat (mode_dir, "dcdata.txt"));
   pop2buf("dcdata.txt");
   set_buffer_hook("par_sep", &dc_parsep);
}

% create a dcdata.txt file for the current buffer from template and guesses
public define jedmodes_make_dcdata()
{
   variable dcdata_template = expand_template_file("dcdata.txt");

   if (file_status(dcdata_template) != 1)
     dcdata_template = dircat(Jedmodes_Dir, "../doc/mode-template/dcdata.txt");
   variable dc_replacements = Assoc_Type[String_Type];
   dc_replacements["<TITLE>"] = path_sans_extname(whatbuf());
   dc_replacements["<REQUIRES>"] = jedmodes_get_requirements();
   dc_replacements["<SUBJECT>"]="";
   bob;
   if (bol_fsearch("% Keywords:"))
     {
	()=ffind(":");
	go_right_1;
	skip_white;
	push_mark_eol;
	bufsubstr;
	()=strreplace(",",";",100);
	dc_replacements["<SUBJECT>"]=();
     }

   bob;
   go_down(Jedmodes_Abstract_Line_No-1);
   dc_replacements["<ABSTRACT>"] = strtrim(get_line(), "% \t");
   variable mode_dir = dircat(Jedmodes_Dir, dc_replacements["<TITLE>"]);
   mkdir (mode_dir, 0755);   % make it world searchable
   chdir (mode_dir);
   write_string_to_file("<?php\n" +
      "// Index file for mode directories, reads mode-index.php with mode argument\n" +
      "$jedmodes_root = dirname(dirname(dirname(__FILE__)));\n" +
      "$title = basename(dirname(__FILE__));\n"+
      "include(\"$jedmodes_root/mode-index.php\");\n?>\n", "index.php");
   % system("ln -s ../../mode-index.php index.php");
   % make the file world readable
   chmod("index.php", 0644);

   jedmodes_find_dcdata();
   if (bobp and eobp())
     insert_template_file(dcdata_template);
   bob;
   foreach(dc_replacements) using ("keys", "values")
     replace();
 }

% upload a file from the private jedmodes mirror to sourceforge
public define jedmodes_upload()
{
   variable from = buffer_filename(),
   to = "milde,jedmodes@web.sf.net:"
     + path_dirname(from[[is_substr(from, "jedmodes")+8:]]);
   save_buffer();
   ishell();
   eob;
   vinsert("\n\n#upload %s to jedmodes\nscp -p %s %s/\n",
	   path_basename(from), from, to);
}

% find the path of the current (or given) mode in the CVS working copy
define jedmodes_find_cvs_path() % (mode=whatbuf(), mode_dir=path_sans_extname(whatbuf()))
{
   variable mode, mode_dir = path_sans_extname(whatbuf());
   (mode, mode_dir) = push_defaults(whatbuf(), mode_dir, _NARGS);
   return path_concat(path_concat(Jedmodes_CVS_Root, mode_dir), mode);
}

#ifexists diff
% compare the current (or given) file with the version in the CVS copy
public define jedmodes_diff() % (mode=whatbuf(), mode_dir=path_sans_extname(whatbuf()))
{
   variable mode, mode_dir = path_sans_extname(whatbuf());
   (mode, mode_dir) = push_defaults(whatbuf(), mode_dir, _NARGS);

   variable path = jedmodes_find_cvs_path(mode, mode_dir);

   !if (file_status(path) == 1)
     error("Jedmodes file doesnot exist: " + path);
   diff(path, buffer_filename());
}
#endif

% commit a new mode version to the CVS
static define commit_mode()
{
   variable file, dir, name, flags;
   (file, dir, name, flags) = getbuf_info();
   variable comment, cvs_dir = path_dirname(jedmodes_find_cvs_path());
   !if(file_status(cvs_dir))
     cvs_dir = path_concat(Jedmodes_CVS_Root,
			    read_mini("Mode Sources Directory",
				      path_sans_extname(name), ""));
   !if(file_status(cvs_dir))
       {
	  if (get_y_or_n("Create " + cvs_dir) != 1)
	    return;
	  % set the umask to 0, so we can have searchable dirs
	  variable old_umask = umask(0);
	  () = mkdir(cvs_dir, 0755);  % world searchable
	  () = umask(old_umask);
       }

   % comment out the debug_info
   bob();
   if (bol_fsearch("_debug_info"))
     comment_line();
   bob();
   % Replace UNPUBLISHED with current date
   bob();
   if (re_fsearch("UNP[U ]......."))
     {
	() = replace_match(get_iso_date()+" ", 1);
   % Get CVS log entry
	skip_white();
	push_mark();
	eol_trim();
	comment = bufsubstr();
     }
   else
     comment = read_mini("CVS Comment:", "", "");
  
   % Copy to the jedmodes mirror
   save_buffer();
   % copy to mode directory (doesnot ask for overwrite)
   if (-1 == copy_file(buffer_filename(),
		       path_concat(cvs_dir, whatbuf()))
       )
     error("could not copy the mode");
   % assure the file is world-readable
   chmod(path_concat(cvs_dir, whatbuf()), 0644);

   % prepare the upload via ishell
   % chdir(cvs_dir);
   ishell();
   eob;
   insert("\n\n#commit to cvs\n");
   push_visible_mark(); % so that the whole new region can be transmitted
   vinsert("cd %s", path_dirname(cvs_dir));
   vinsert("\ncvs commit -m '%s' %s", comment, path_basename(cvs_dir));
   go_up_1();
}

% upload a new mode version from the personal library (CVS and dcdata.txt)
public define jedmodes_new_version()
{
   variable name = whatbuf();
   % commit new version to CVS
   commit_mode();
   % update the dcdata file
   sw2buf(name);
   jedmodes_find_dcdata();
   if (fsearch("date:"))
     {
	skip_word();
	skip_non_word_chars();
	del_eol();
	insert_iso_date();
     }
   jedmodes_upload();
   pop2buf("dcdata.txt");
}

% Add a new mode from the personal library
% Best called from the open buffer of the new mode
public define jedmodes_new_mode()
{
   % Description file dcdata.txt
   variable buf = whatbuf();
   variable mode_dir = dircat(Jedmodes_Dir,
      read_mini("Mode Home Directory", path_sans_extname(whatbuf()), "")),
   to = "milde,jedmodes@web.sf.net:"
     + (mode_dir[[is_substr(mode_dir, "jedmodes")+8:]]);

   % create dcdata.txt file in the jedmodes mirror
   % set the umask to 0, so we can have searchable dirs
   variable old_umask = umask(0);
   !if(file_status(mode_dir))
     () = mkdir(mode_dir, 0755);  % world searchable
   () = umask(old_umask);
   jedmodes_make_dcdata();
   save_buffer_as();
   % assure the file is world-readable
   chmod(dircat(mode_dir, "dcdata.txt"), 0644);

   % prepare the dcdata upload via ishell (creating the directory)
   ishell();
   eob;
   insert("\n\n#upload the mode description\ncd " + mode_dir);
   vinsert("\nscp -pr %s %s", ".", to);

   % copy the source to the CVS tree and prepare the commit via ishell
   sw2buf(buf);
   %
   % add mode to CVS and commit
   commit_mode();
   sw2buf("*ishell*");
   bol_bsearch("cvs commit");
   bol();
   vinsert("cvs add %s", path_basename(mode_dir));
   vinsert("\ncvs add %s\n", path_concat(path_basename(mode_dir), buf));
   sw2buf("dcdata.txt");
}

% download 'expanded' dc metadata for a Jedmodes mode from the sf website
public define jedmodes_fetch_dcdata() % ([mode])
{
   !if (_NARGS)
     read_mini("Fetch metadata for mode:", "", "");
   variable mode = ();

   find_url(sprintf("jedmodes.sf.net/mode-index.php?mode=%s&format=text/plain", mode));
   rename_buffer(mode + "-dcdata.txt");
}
