#gallery_cmd.py
"""Command line interface to main_gallery.
For quicker loading, this module is called by the few-line make_gallery.py.
"""

import getopt
import os
import sys

from gallery_param import *
import main_gallery

GALLERY_ENV = "GALLERY_ARGS"

def default_dim_sizes():
    p = Gal_Param()
    return (make_opt_size_str(p.thumb_option, p.thumb_dim),
            make_opt_size_str(p.medium_option, p.medium_dim))

def make_opt_size_str(opt, dim):
    opt_str = ""
    if opt: opt_str = "?"
    return "%s%dx%d" % (opt_str, dim[0], dim[1])

(THUMB_SIZE, MEDIUM_SIZE) = default_dim_sizes()
    
def bad_exit(msg, n):
    """Abort the program with a message."""
    if msg:
        print "ERROR: " + msg
    usage_summary()
    if msg:  # at both ends since summary is long
        print "ERROR: " + msg
    sys.exit(n)


def get_char_flag(s, allowed, opt, n):
    """ Return s[0] if it is allowed, or abort the program."""
    if not (s and s[0] in allowed):
        bad_exit("Valid -%s options are among the characters: %s."
                  % (opt, allowed), n)
    return s[0]


TRUE_CHAR = "yYTt+"
FALSE_CHAR = "nNfF-"
BOOL_CHAR = TRUE_CHAR + FALSE_CHAR

def get_boolean_flag(s, opt, n):
    """Return the boolean associated with flag s[0], or abort the program.""" 
    ch = get_char_flag(s, BOOL_CHAR, opt, n)
    return ch in TRUE_CHAR


def get_int_flag(s, low, high, opt, n):
    """Return the int associated with a flag, or abort the program.""" 
    try:
        i = int(s)
    except:
        bad_exit("A -%s value must be an integer." % opt, n)
    if i < low or i > high:
        bad_exit("A -%s value must be from %d to %d."
                  % (opt, low, high), n)
    return i


def parse_dim(s, min, max, name, n):
    """ Return (optional, (w,h)) parsed from '#x#' or '?#x#'
    Abort if s is not a legal dimension string."""
    s = s.strip()
    optional = s.startswith('?')
    if optional:
        s = s[1:]
    try:
        i = s.index('x')
        w = int(s[:i])
        h = int(s[i+1:])
    except StandardError:
        bad_exit(("Valid -%s dimensions are in the form #x# or ?#x#, \n" +
                 "such as 160x120 or ?300x300.") % name, n)
    if not ((min <= w <= max) and (min <= h <= max)):
        bad_exit("A -%s dimension must be from %d to %d." % (name, min, max), n)
    return (optional, (w, h))    


import string

def str2args(s):
    """ Return a list of white-not-between-quotes separated tokens.
    Instead return None if there is an unmatched quote."""
    
    quotes_allowed="\"\'"
    active_quote = ""
    start = -1
    list = []
    s += " " # so not in a word at the end
    for i in range(len(s)):
        ch = s[i]
        if active_quote:
            if ch == active_quote:
                active_quote = ""
        elif ch in string.whitespace:
            if start != -1:
                list.append(s[start:i])
                start = -1
        else:
            if start == -1:
                start = i
            if ch in quotes_allowed:
                active_quote = ch
    if active_quote:
        return None
    return list


def get_opts(argv, errstr=""):        
    try:
        return getopt.getopt(argv, "n:o:u:r:b:g:t:p:f:l:hHc",
                  ["new_gal_under", "originals", "use_titles", "remove_blanks", 
                   "big_pics_linked", "gallery", "thumbsize", "picsize",
                   "force_conversion", "lines_in_thumb_label", "help",
                   "HELP", "console"] )
    except getopt.GetoptError:
        bad_exit(errstr,2)

def processCmdLine(argv):
    default = os.getenv(GALLERY_ENV, "").strip()
    if default:
        first = str2args(default)
        if not first:
            bad_exit(("Environment variable %s has an unclosed quote:\n%s"
                     % (GALLERY_ENV, default)), 20)
        # next line only for possible error abort
        get_opts(first,"Bad environment string:\n  %s=%s"
                                                   % (GALLERY_ENV, default))
        argv = first + argv
        
    (opts, args) = get_opts(argv)

    src_dirs = args

    # set defaults
    param = Gal_Param()

    for opt, val in opts:
        if opt in ("-h",  "--help"):
            print usage_summary()
            sys.exit()
        if opt in ("-H",  "--HELP"):
            print usage()
            sys.exit()
        if opt in ("-n", "--new_gal_under"):
            param.new_gal_under = val            
        elif opt in ("-o", "--originals"):
            param.orig_pic_action = get_char_flag(val, "mcl", "o", 3)            
        elif opt in ("-u", "--use_titles"):
            param.use_titles = get_boolean_flag(val, "u", 4) # or abort
        elif opt in ("-r", "--remove_blanks"):
            param.remove_blanks = get_boolean_flag(val, "r", 10) # or abort
        elif opt in ("-b", "--big_pics_linked"):
            param.big_pics_linked = get_boolean_flag(val, "b", 5) # or abort
        elif opt in ("-g", "--gallery"):
            param.gallery_title = val            
        elif opt in ("-t", "--thumbsize"):
            param.thumb_option, param.thumb_dim = \
                                    parse_dim(val, MIN_THUMB, MAX_THUMB, "t", 6) 
        elif opt in ("-p", "--picsize"):
            param.medium_option, param.medium_dim = \
                                  parse_dim(val, MIN_MEDIUM, MAX_MEDIUM, "t", 7) 
        elif opt in ("-f", "--force_conversion"):
            param.force_conversion = get_boolean_flag(val, "f", 8) # or abort
        elif opt in ("-l", "--lines"):
            param.lines_in_thumb_label = \
                      get_int_flag(val, 0, MAX_THUMB_TITLE, "l", 9) # or abort
        elif opt in ("-c", "--console"):
            param.console = True
    return (args, param)
#-----------------------------------------------


def noGUI(args, param):
    "run noninteractively with console output."
    try:
        print main_gallery.make_gallery(args, param) + " created."
    except Exception, e:                    
        bad_exit(str(e) + "\nAborting gallery creation!!!!!!!!!!!!!", 21)


#-- Documentation generation -----------------------------------------------

def usage_string(): 
    """Create a picture gallery.
python make_gallery <arguments> <existing picture directories>
python gallery_cmd <arguments> <existing picture directories>

The second version is a noninteractive console version.  The first version
allows the use of a graphical interface.  Parameters are optional except
that gallery_cmd expects an existing picture directory (pic dir for short).
Any pic dir after the first is merged into the first one.  The command 
line arguments are implicitly preceded by any arguments in the environment 
variable GALLERY_ENV.
--------------------------------------------------------------------------
Summary of command line arguments:

Formats and Examples
  Explicit choices of values are separated by '|':  yes|y|no|n
  Expected integer values are indicated by #: #x# would allow 120x90
  Descriptions of values are placed between angle brackets: <existing dir>

Flag             Values                      Default value
Short Long    
-c --console                                 false
-n --new_dir_under= <existing dir>           
-o --originals= move|m|copy|c|leave|l        move
-u --use_titles_in_files= yes|y|no|n         yes
-r --remove_blanks= yes|y|no|n               no
-b --big_pics_linked= yes|y|no|n             yes
-g --gallery= <title>                        
-t --thumbsize= #x# | ?#x#                   THUMB_SIZE
-p --picsize= #x# | ?#x#                     MEDIUM_SIZE
-f --force_conversion= yes|y|no|n            no           
-l --lines_in_thumb_label= #                 2
-h --help
-H --HELP

Use the argument -H to see the FULL documentation
===========================================================================
Introduction
    This gallery program is designed to take a folder of images with
extension PIC_EXT, and create web pages with an index containing an
optional introduction and thumbnails, with each thumbnail linked to a page 
which contains a single larger image plus title, navigation buttons, and an 
optional description.  There are interactive and purely commandline versions.  
Some less-used options are presently only allowed as commandline options.  
The full commandline documentation is generated with the flag -H.
    The principal feature of this program is the easy generation of the 
text and filenames that are in the gallery from a single simple text file
(the pic docs).  The interactive version automatically handles the file 
editor.  It also displays any newly created gallery in your default browser.
    You can look at the actual appearance while editing a further version.
The gallery index is set up to adjust to the width of your browser window,
varying the number of thumbnails in a row.  Thus you can always resize your 
browser window to sit beside the text you are editing.
    If you want changed names or added titles or information, you will
want to create a gallery twice.  The first time generates the gallery 
structure for you to browse, and generates a bare bones pic docs file for 
you to elaborate.  After you modify the text file, quicky regenerate the
gallery to see the effect of your changes.  (And you can continue this
cycle if you have more changes.)  
    If you have web page space, you just need to upload the gallery directory
and upload the style sheet, gallery.css, so it remains in the parent directory 
of the gallery.  Then your friends can see the gallery, too.
    Many digital cameras produce pictures much too large for a computer screen.
The gallery program produces two smaller versions:
       1.  A display version scaled to fit on the page dedicated to that 
           picture, its explanatory text, and navigation links.
       2.  A thumbnail for the gallery index, linked to the page described 
           in 1.
By default the original digital images are also linked to the page with the 
display version.  This is useful on a web server if you want your friends to 
be able to print large copies, though it makes the space required for the 
gallery much larger!
    When the program scales a picture, it always scales the same amount in 
each direction to avoid distortion.  Hence only one of the maximum dimensions 
specified for thumbnails or the display version may be actually reached. 
    Besides the maximum sizes for scaled image, there are a number of program 
parameters that can be set in the interactive version.  They are described 
in the tooltips for the various controls.
    There is an extra parameter for scaled images like the thumbnails:
whether the dimensions setting are always applied or only initially.  When 
the program initially creates a gallery, it always is guided by the dimensions 
you have specified.  When modifying a gallery, which already has scaled images, 
the program assumes you want to keep the original scaled image dimensions 
unless tell it to always use the sizes you currently specify in the program.
    It is necessary for each thumbnail to be the same size to allow the index 
web page to adjust the number of thumbnails in each row based on the browser 
window width.  Though the program can make the thumbnail images the same size, 
it cannot predict exactly how different browsers will handle the text in 
titles.  Hence the user may adjust the program parameter that selects to number 
of lines to allow for titles under thumbnails.  
    If you choose 0, no titles are displayed with the thumbnails, and the 
index is the most compact, and there should not be unequal size problems.  
    If you choose a value greater than 0, thumbnails get titles displayed with
them.  If the index ends up with unequal sized entries, some title is taking 
more lines than you specified.  Increase the number and remake the gallery.  
(Some browsers sometimes start to create a page before the entire page has 
been transmitted, and may make errors as a result.  The first thing to do if 
the entries do not appear the same size is to redisplay your browser screen 
and see if the problem goes away.)
============================================================================== 

Picture Documentation File Format:

    To rotate pictures or provide titles and descriptions, you need to edit
the picture documentation, a plain text file.  An example file is shown on 
the left with explanations of each line shown on the right:

Example PICDOCFILE          Role of each line
-----------------------------------------------------------------------
My Pics                      Gallery title
                             Empty line for human reader
These are pictures from      Intro text for the gallery index,
my birthday.                   more Intro text run together.
                             Empty line for human reader
###PICT0001 >                File PICT0001PIC_EXT:  rotate clockwise
Blowing Out The Candles      Picture's title
                             Empty line for human reader
I am a blowhard!             Picture's explanatory text
                             Empty line for human reader
###PICT0002 <                File PICT0002PIC_EXT: rotate counter-clockwise
Conversation                 Picture title (no text afterward)
                             Empty line for human reader
###PICT0003                  File PICT0003PIC_EXT (no rotation)  
The Group                    Picture's title
This is paragraph 1          Picture's explanatory text starts,
and more of paragraph 1.       rest of paragraph wrapped together.
                             Blank separator in text for a new paragraph
This is paragraph 2.         More of the picture text...
-----------------------------------------------------------------------

The general format of the components followed by this example is below.  
Merely for this description, each component description is enclosed in 
square brackets:

     [gallery title - one line]
     [multiline text of gallery introduction]
     ###[original pic file name base][rotation]
     [pic title - one line - keep it short]
     [multiline text of picture description]

     ... more pictures and their data, each starting with a line starting
     with the marker ###

The only required parts are lines with "###" and the original pic file names 
(which are always provided ahead of time by the program).  All pic files are 
implicitly assumed to end with PIC_EXT, so it is added automatically to the 
base in the file.  The original pic file names are used to find pictures.  If 
a title is provided, the file is automatically renamed with the new name 
derived from the title.  By default the new file name has blanks and special 
symbols replaced by '_'.  The file extension PIC_EXT is always added to the end. 
    Picture titles do not need to be distinct.  Filenames get a number added
to them, if necessary, to keep them distinct.
    If blanks are present in file names, double quotes must enclose the file 
names when they are used on the command line or in PICDOCFILE .
    
    At the end of the file line you may give instructions for rotating the 
picture, indicating with angle brackets the direction to rotate the top of 
the picture and number of times to rotate by 90 degrees:

   <  : rotate 90 degrees counter-clockwise
   >  : rotate 90 degrees clockwise
   >> : rotate 180 degrees

    If a title is omitted, the multi-line text after it must also be omitted.  
The multi-line text is displayed as html, so lines are wrapped.  One 
transformation performed by the program is that double carriage returns 
are replaced by new paragraphs.  For now, other html markup that is 
desired must be added explicitly.
    Except in the middle of multi-line text, blank lines may be introduced,
to enhance human readablity, if desired, with no effect on the gallery.
    The order of the pictures in the gallery is the order in the text file.
If you want to exclude pictures or alter their order in the gallery, make the
corresponding deletion or reordering in the text file.

    Making a gallery transforms its text file to match, changing file names,
and removing rotation instructions, since they are already done.  The new 
file and gallery are available for further editing:
    
Example PICDOCFILE After Making Its Gallery     
--------------------------------------------
My Pics                 
                        
These are pictures from 
my birthday.            
                        
###Blowing_Out_The_Candles           
Blowing Out The Candles 
                        
I am a blowhard!        
                        
###Conversation           
Conversation            
                        
###The_Group             
The Group               
This is paragraph 1     
and more of paragraph 1.
                        
This is paragraph 2.    
=============================================================================

Command Line Argument Descriptions

    ---------------------------------------------------------------------
-c --console                                 false
    Ignored by gallery_cmd and forces make_gallery to to non-interactive.
    By default make_gallery uses an interactive graphical interface.    
    ---------------------------------------------------------------------
-n --new_dir_under= <existing dir>              
    If an existing directory is specified in this option, the gallery 
    files end up in a subdirectory under this directory, with the 
    subdirectory name derived from the gallery title.  It is a completely 
    new directory unless it is the same full name as the first pic dir. 
         If this parameter is not specified, the file additions and 
    transformations to make the gallery go on in the first pic dir, 
    (possibly renamed -- see the description of --title).  No new 
    directory is created.  
    ---------------------------------------------------------------------
-o --originals= move|m|copy|c|leave|l        move
    Specifies what to do with the original pictures if a new destination 
    directory is specified by the --new_dir_under option.  Files may be 
    moved or copied to the new dir, or left in the original directory.  
    If no new directory is created, the only legal value is the default, 
    'move'.  If pic file names are changed by PICDOCFILE , then the moved 
    or copied files are renamed.  Only with the option leave do the 
    original pictures necessarily retain their original names.  The leave 
    option must be combined with option --big_pic_links=no.
    ---------------------------------------------------------------------
-u --use_titles_in_files= yes|y|no|n         yes
    With a yes value, use each pic's title as a basis for a new name for 
    the picture file, if no new name is specified by PICDOCFILE .  With a 
    value no, the original pic names are kept unless a new name is 
    explicitly given in PICDOCFILE .
    ---------------------------------------------------------------------
-r --remove_blanks= yes|y|no|n               no
    This flag affects the automatic conversion of proposed names for the 
    gallery directory and image files to the actual names, and should be set 
    consistent with the operating system and personal preference.
        With a yes value, characters excluding '+', '-', letters, and 
    numbers are replace by underscores, making an intelligible one-word name 
    that should be acceptable in any operating system.
        With a no value, names are only modified to be legal Windows file
    names.  If blanks are present, double quotes must enclose the file names
    when they are used on the command line or in PICDOCFILE .  
    ---------------------------------------------------------------------
-b --big_pics_linked= yes|y|no|n             yes
    With a yes value, there are three sizes of pictures linked into the 
    gallery, the thumbnails in the index, medium sized versions shown one 
    at a time in sequence, and the original size, presumably larger, 
    pictures are linked from the page with the corresponding medium sized 
    picture.  With a 'no' value, the links to the original sized pictures 
    is omitted.  The no option is required if --originals=leave.
    ---------------------------------------------------------------------
-g --gallery= <title>                           
    Use this title for the gallery, overriding any other title in 
    PICDOCFILE.  The gallery directory name is always derived from the 
    title, with blanks and punctuation replaced by '_'.  Remember to put 
    the title in quotes if it is more than one word.  If no 
    --new_dir_under is specified, the first pic dir is renamed to the 
    gallery directory name.  If a --new_dir_under value is specified, the 
    gallery directory appears under that directory, and it is a 
    completely different directory from the original pic dir, unless the 
    full gallery directory path matches the original pic dir.
    ---------------------------------------------------------------------
-t --thumbsize= #x# | ?#x#                   THUMB_SIZE
    Since # stands for an integer, examples values would be 160x120 or 
    ?200x200 (no embedded spaces). The two numbers are a width and height 
    in pixels.  The form without a '?' forces this size for the 
    thumbnail.  The form with a '?' makes this be the size if the gallery 
    is being created for the first time.  After a gallery is created, if 
    '?' starts the parameter value, an old size in the first pic dir is 
    preserved, and the dimensions after -t ? are ignored.  There is an 
    error if the sizes in the first pic dir are not consistent.
    
    If some pictures in the gallery are rotated, it is best to make width 
    and height be the same number.
    ---------------------------------------------------------------------
-p --picsize= #x# | ?#x#                     MEDIUM_SIZE
    This controls the maximum width and height in pixels of the pictures 
    show in sequence, one to a page, just as the -t option controls the 
    size of thumbnails, except with the '?', pic sizes are allowed to 
    vary without triggering an error, and new pictures are created with 
    the maximum dimensions of the existing pictures in the first pic dir. 
    ---------------------------------------------------------------------
-f --force_conversion= yes|y|no|n            no           
    If yes, all thumbnails and medium sized pics will be recreated from 
    the originals.  It is an error if the originals are not present.  If 
    no, these time-consuming conversions only take place if a rotation is 
    specified, or if there is no existing converted picture files of a 
    size indicated by the -t or -p dimensions specified as make_gallery 
    parameters.  
         The -f y option might be specified if some outside processing 
    was done to an original picture like cropping or color correction, 
    after the gallery was first created.  If a change was only made to a 
    single picture, it may be less time consuming to just remove the 
    thumbnail and medium sized versions of the picture, and rerun the 
    gallery program without the -f option.  Then the picture conversion 
    will only be done where needed.
    ---------------------------------------------------------------------
-l --lines_in_thumb_label= <number>          2
    The number of text lines provided under index thumbnails for picture 
    titles.  For the index pictures to be laid out neatly, it is 
    important that this be big enough to hold the longest picture title, 
    as it wraps around.  If 0 is specified, no title is shown under index 
    thumbnails.  Excess lines reduce the number of thumbnails that can 
    appear on the screen at once.  Short titles help.  
    ---------------------------------------------------------------------
-h --help
    Show summary documentation and exit.
    ---------------------------------------------------------------------
-H --HELP
    Show this help full documentation and exit.
===========================================================================

Using the Command Line Version

Suppose directory pics-08-06-04 has the pictures you want and the directory 
is a subdirectory of your current directory.  First make a gallery from all 
PIC_EXT files, and create a basic file PICDOCFILE:
 
    python gallery_cmd.py pics-08-06-04

This adds files to directory pics-08-06-04, including index.html.  While 
viewing a browser starting from the index, edit and save 
pics-08-06-04/PICDOCFILE (using the format above), and then rerun the 
gallery program to incorporate the changes you made in the gallery itself
and the PICDOCFILE file:

    python gallery_cmd.py pics-08-06-04

If the title supplied for the gallery in PICDOCFILE  was "Andy's Bday", 
the directory pics-08-06-04 is renamed to Andy_s_Bday, and this name 
should have been printed out by the program as a reminder.  Picture files 
are also renamed in the renamed directory, with the file names derived 
from any picture titles you added in PICDOCFILE , unless you added 
explicit new file names.  The new web pages reached from index.html are 
created to match your documentation.

Caution:  Your operating system will likely not allow a current directory
to be the picture directory when the program goes to change the directory 
name.
 
If you want to further tinker with the gallery descriptions, titles, and 
orientations, again edit PICDOCFILE .  You will find that the program has 
rewritten the file to indicate the new file names.  If you want your 
previous version later, it is renamed, adding a suffix ".old" or if you 
redit and rerun several times, a suffix ".old_1", ".old_2", ... is added.
----------------------------------------------------------------------------
Further parameter examples:
 
If in the first pass, you entered

  python gallery_cmd.py -o c -n c:/pics -t "My Pics" c:/raw/pics-08-06-04

or used the more verbose long line:  
  python gallery_cmd.py --originals copy --new_dir_under c:/pics --title 
"My Pics" c:/raw/pics-08-06-04

then you would leave the original pics in the original c:/raw/pic-08-06-04 
directory and put copies into a new gallery (-o c) with title "My Pics" 
(-t "My Pics") in the subdirectory My_Pics of the directory c:/pics 
(-n c:/pics).  The web index file would be c:/pics/My_Pics/index.html.

If instead you had entered

  python gallery_cmd.py -n . -t "My Pics" -o l -b n pics-08-06-04

you would create a gallery entitled "My Pics" in a new directory My_Pics 
under the current directory (-n .).  You would leave the original pics in 
the original pic-08-06-04 directory and not copy them to the gallery  
(-o l), so the web pages for the gallery must not link to the big original 
pictures (-b n).
     
If you next entered

  python gallery_cmd.py My_Pics pics-08-07-04

you would move all the pictures and their documentation from the gallery 
in the directory pics-08-07-04 so they are added to the existing gallery 
in the directory My_Pics.  The text of the galleries' introductory 
documentation is automatically joined when one or more additional 
galleries are merged as in this case, so you probably want to edit the 
combined introductions in the new pic doc file, and maybe reorder the 
pictures before recording these changes in the gallery by a further call 
to

  python gallery_cmd.py My_Pics
----------------------------------------------------------------------------  
Environment Variable Settings

    If you do not like any of the default parameter values, you can set
an environment variable, GALLERY_ENV to have the parameter settings you
want.  For instance if you prefer to leave blanks in your file names and 
like 200x200 thumbnails always, set the environment variable to
  -rn -t200x200
You can still override these from the command line.
=============================================================================
"""
    return replaceConstants(usage_string.__doc__)

def usage():
    s = usage_string()
    # tack summary from beginning also on end
    return s + "\nRepeated Commandline Summary:\n\n" + usage_summary(s)
    
def replaceConstants(src):
    main_dict = globals() 
              
    for n in ("PICDOCFILE", "PIC_EXT", "GALLERY_ENV",
              "THUMB_SIZE", "MEDIUM_SIZE"):
        src = src.replace(n, main_dict[n])   
    return src

def usage_summary(s = None):
    if not s:
        s = usage_string()
    i = s.find("Introduction")
    return s[:i]

def intro():
    s = usage_string()
    i = s.find("Introduction")
    j = s.find("Command Line Argument Descriptions")
    return s[i:j]

# -------- Run as main program -------------------------------------------

def main(argv):
    args, param = processCmdLine(argv)
    noGUI(args, param)

if __name__ == "__main__":
    main(sys.argv[1:])

# end of make_gallery.py ---------------------------------------------
