1 #!/usr/bin/env python 2 # 3 # 'homepage.py' = 'Organize http://www.lafn.org/~cymbala/index.html' 4 # 5 # See also: http://packages.debian.org/unstable/web/sitecopy.html 6 # 7 # Time-stamp: <2003-08-07 14:34:06 cymbala> 8 # Started: 2000-07-06 9 # 10 # THIS SCRIPT MAKES SURE FILES ON INTERNET WEB SITE ARE SYNCHRONIZED 11 # WITH ORIGINAL FILES ON LAPTOP. 12 # 13 # Scheduled upload: 14 # ~root crontab: 58 3 * * * pon; sleep 15m; poff 15 # ~cymbala crontab: 59 3 * * * python ~/Db/Homepage/homepage.py 16 # -------------------------------------------------------------------- 17 18 import os 19 import cPickle 20 import fileinput 21 import ftplib 22 import pre 23 import sys 24 import string 25 import tempfile 26 import time 27 import types 28 29 30 HOME_DIR = os.path.dirname(os.path.expanduser('~/')) 31 WORK_DIR = os.path.dirname(os.path.join(HOME_DIR, 'Db/Homepage/')) 32 HTML = 'html' 33 XML = 'xml' 34 35 36 # Pick debug level (does not need FTP connection), or "None". 37 # Another way to debug is to make MAX_BYTES very low. 38 debug = 'p' # w/ paging 39 debug = 't' 40 debug = None 41 42 # Display paths and filenames as they are modified. 43 # Not related to plain 'debug'. 44 debug_names = 't' 45 debug_names = None 46 47 48 # Pieces used twice or more in "information": 49 PYLOG = 'homepage.log' 50 PYNAME = 'homepage.py' 51 PYDATA = os.path.join(WORK_DIR, 'homepage_py.tab') 52 PICKLE_NAME = 'homepage.cpickle' 53 54 55 info = { 56 None: None, 57 # Comments OK after comma ... 58 59 'DEBUG': debug, 60 'DEBUG_NAMES': debug_names, 61 62 'GOOGLE_URL': 'http://www.google.com/', 63 # groups.google.com has a radio-button to switch between the two. 64 65 'PYLOG': PYLOG, 66 'PYLOG_PATHFILE': os.path.join(WORK_DIR, PYLOG), 67 # ... Log file. 68 69 'LS_SWITCHES': '--dereference ', 70 # ... note traililng space. 71 72 'PICKLE_PATHFILE': os.path.join(WORK_DIR, PICKLE_NAME), 73 # Use a pickle to store data between executions. 74 75 'PYDATA': PYDATA, 76 'PYNAME': PYNAME, 77 'PY_PATHFILE': os.path.join(WORK_DIR, PYNAME), 78 'PYDATA_HTML': 'homeptab.' + HTML, 79 'PYNAME_HTML': 'homep_py.' + HTML, 80 'PYLOG_HTML': 'homep_lg.' + HTML, 81 # ... name of this script, and 82 # ... location and name of this script, and 83 # ... name of this script as an HTML file. 84 85 'PY2HTML': '/usr/local/lib/site-python/lpy.py', 86 'PY2HTML_OPTIONS': '-noindex', 87 # ... Use a Python script to convert any Python script to HTML. 88 89 'SYS_PATH_APPEND': ['~/Db', '~/Db/Homepage'], 90 # Additional places for import to find stuff. 91 92 'HOME_DIR': HOME_DIR, 93 'WORK_DIR': WORK_DIR, 94 # Working and home directories. 95 96 'HTML': HTML, 97 'XML': XML, 98 None: None 99 } 100 101 102 # Dictionary that specifies which files from here are to be uploaded. 103 # Entries are in: "homepage_py.tab". 104 # NOTE: 'homepage.py.html' (not 'homepage.py') is uploaded due to PY2HTML. 105 # 106 info['Here2There'] = {} 107 108 109 # 110 class Homepage: 111 """Upload files to Web site and modify on-the-fly.""" 112 113 def listl(self, mystring): 114 listing = os.popen(string.join( 115 ['ls -l', info['LS_SWITCHES'], mystring])) 116 dir_lines = listing.readlines() 117 listing.close() 118 return dir_lines 119 120 def listl_dict(self, listl_raw): 121 """Parse output from "dir" (either local or remote "dir").""" 122 123 # - listl_raw is raw output from LS command. 124 # - dict is dictionary for storing final results. 125 dict = {} 126 for line in listl_raw: 127 parts = string.split(line) 128 129 # ['-rw-r--r--', 130 # '1', '7695', '1010', '3289', 131 # 'Jul', '18', '02:27', 'homepage.py'] 132 # 133 if len(parts) == 9: 134 # 135 # Ignore permissions and user/group info. 136 # Keep type. 137 parts[0:4] = [parts[0][0], '', '', ''] 138 139 # Record subdirectories separate from files. 140 # 141 # match_obj = self.re_Subdirectory.search(parts[0]) 142 # if match_obj == None: 143 # 144 dict[parts[len(parts) -1]] = parts 145 pass 146 pass 147 # --- 148 return dict 149 150 def LogMsg(self, message): 151 """Message handler (linefeed is appended by this attribute).""" 152 153 # 1. 154 # Write to file (assuming message does not have ending '\n'): 155 # file.write(message + '\n') 156 self.LOG.write(message + '\n') 157 158 # 2. 159 # Print to std-out (usually '*Python Output*' buffer): 160 print message 161 162 # --- 163 pass 164 165 def debug_names_section_footer(self): 166 if info['DEBUG_NAMES']: 167 msg = '_____ 999> ' 168 self.LogMsg(string.ljust(msg, 32)) 169 pass 170 pass 171 172 def Add2Here2There(self, first_arg, second_arg): 173 """Add key/value pair to Here2There dictionary.""" 174 175 # If order of columns is reversed, just reverse this: 176 info['Here2There'][first_arg] = second_arg 177 pass 178 179 def __init__(self, info): 180 """What to do when class instantiated.""" 181 182 info['re'] = {} 183 info['re']['comment'] = pre.compile('^[\s]*#') 184 info['re']['blank_line'] = pre.compile('^[\s]*$') 185 info['re']['number'] = pre.compile('^[0-9]+$') 186 info['re']['html_ext'] = pre.compile('\.' + info['HTML'] + '$') 187 info['re']['xml_ext'] = pre.compile('\.' + info['XML'] + '$') 188 info['re']['placeholder'] = pre.compile('__([a-z]+(_[a-z]+)*)__', 189 pre.I) 190 191 # Log file. 192 info['PYLOG_PATHFILE_OLD'] = None 193 if os.path.isfile(info['PYLOG_PATHFILE']): 194 info['PYLOG_PATHFILE_OLD'] = info['PYLOG_PATHFILE'] + '.old' 195 os.rename(info['PYLOG_PATHFILE'], 196 info['PYLOG_PATHFILE_OLD']) 197 self.Add2Here2There(info['PYLOG_PATHFILE_OLD'], 198 info['PYLOG_HTML']) 199 pass 200 # 201 # OPEN LOG FILE. 202 self.LOG = open(info['PYLOG_PATHFILE'], 'w') 203 204 # Read program data from external file. 205 if os.path.isfile(info['PYDATA']): 206 for line in fileinput.input(info['PYDATA']): 207 if (not info['re']['comment'].match(line)) and \ (not info['re']['blank_line'].match(line)): 209 # 210 if line[-1] == '\n': line = line[:-1] 211 data = string.split(line, '\t') 212 213 if not data[0] in info.keys(): 214 if len(data) == 2: 215 spam = data[1] 216 if info['re']['number'].match(spam): 217 spam = int(spam) 218 pass 219 info[data[0]] = spam 220 pass 221 elif len(data) == 3: 222 spam = data[2] 223 if info['re']['number'].match(spam): 224 spam = int(spam) 225 pass 226 info[data[0]] = {data[1]: spam} 227 pass 228 else: 229 raise 'Too many values: ' + line 230 pass 231 else: 232 # Repeating keys (2nd+ occurrences). 233 # Convert to a list: 234 if type(info[data[0]]) == types.StringType: 235 info[data[0]] = [info[data[0]]] 236 pass 237 238 if type(info[data[0]]) == types.DictType: 239 if len(data) == 3: 240 spam = data[2] 241 if info['re']['number'].match(spam): 242 spam = int(spam) 243 pass 244 if (data[1] in info[data[0]].keys()) and \ (info[data[0]][data[1]] == spam): 246 raise 'Key already exists: ' + line 247 else: 248 info[data[0]][data[1]] = spam 249 pass 250 pass 251 else: 252 raise 'Need exactly three parts: ' + line 253 pass 254 elif type(info[data[0]]) == types.ListType: 255 if len(data) == 2: 256 spam = data[1] 257 if info['re']['number'].match(spam): 258 spam = int(spam) 259 pass 260 info[data[0]].append(spam) 261 pass 262 else: 263 raise 'Need exactly two parts: ' + line 264 else: 265 raise 'Unknown type for: ' + data[0] 266 pass 267 pass 268 pass 269 pass 270 271 # Absolute exceptions. 272 # 273 # 2001.12.04 274 # info['MAX_BYTES_EXCEPTIONS'].append(info['PYNAME']) 275 276 # Upload just one copy of this script. 277 info['PYNAME_HTML_ABSOLUTE'] = 'http://' + \ info['SYSTEM_NAME'] + \ '/~cymbala/' + \ info['PYNAME_HTML'] 281 if info['USER_NAME'] == 'cymbala': 282 self.Add2Here2There(info['PYNAME'], info['PYNAME_HTML']) 283 pass 284 self.Add2Here2There(info['PYDATA'], info['PYDATA_HTML']) 285 286 # 287 self.month_day_year = time.strftime( 288 "%B %d, %Y", time.localtime(time.time())) 289 290 # Depth to recurse looking for subdirectories on site. 291 info['DIR_SITE_recurse_depth'] = 5 292 293 # Position of byte-count in "ls -l" output. 294 info['DIR_byte_position'] = -5 295 296 # Append paths to sys. 297 for spam in info['SYS_PATH_APPEND']: 298 sys.path.append(os.path.expanduser(spam)) 299 pass 300 301 # Import user-defined stuff after sys-paths appended. 302 import db_user_def 303 self.db_user_def = db_user_def 304 305 # Directory listing variables. 306 self.DIR_ = ['DIR_SITE_past', 307 'DIR_LOCAL_past', 308 'DIR_SITE_current', 309 'DIR_LOCAL_current'] 310 self.DIR_.sort() 311 312 # Keep track of subdirectories to update DIR_SITE_current later. 313 info['DIR_SITE_received_upload'] = [] 314 315 pass 316 317 def pickle(self, garnish): 318 """Pickle to store info between executions.""" 319 320 self.LogMsg('Pickling: ' + garnish[0]) 321 # [0] is action (WRITE, READ, TRANSFER). 322 # [1] is object to WRITE. 323 324 if garnish[0] == 'WRITE': 325 spam = open(info['PICKLE_PATHFILE'], 'w') 326 p = cPickle.Pickler(spam) 327 p.dump(garnish[1]) 328 spam.close() 329 pass 330 # 331 elif garnish[0] == 'READ': 332 spam = open(info['PICKLE_PATHFILE'], 'r') 333 u = cPickle.Unpickler(spam) 334 burp = u.load() 335 spam.close() 336 if info['DEBUG'] == 'p': 337 self.LogMsg(str(burp)) 338 self.__call_paging_program__() 339 raise str(burp) 340 else: 341 return burp 342 # 343 elif garnish[0] == 'TRANSFER': 344 # Get from pre-existing pickle if parameters match. 345 self.cucumber = self.pickle(['READ']) 346 if (self.cucumber['USER_NAME'] == info['USER_NAME'] and 347 self.cucumber['SYSTEM_NAME'] == info['SYSTEM_NAME']): 348 pass 349 else: 350 raise 'Conflicting USER_NAME and/or SYSTEM_NAME!' 351 352 # Before! 353 # 1 of 2: LOCAL. 354 if not info.has_key('DIR_LOCAL_past'): 355 info['DIR_LOCAL_past'] = self.cucumber['DIR_LOCAL_current'] 356 pass 357 # 2 of 2: SITE. 358 if not info.has_key('DIR_SITE_past'): 359 info['DIR_SITE_past'] = self.cucumber['DIR_SITE_current'] 360 pass 361 # 362 # To prevent making a subdirectory on site that already 363 # exists, create a tree with all branches. 364 info['DIR_SITE_past_tree'] = self.branches( 365 info['DIR_SITE_past'].keys()) 366 367 # Increment iteration. 368 info['ITERATION'] = self.cucumber['ITERATION'] + 1 369 370 pass 371 # 372 else: 373 raise 'Unrecognized garnish: ' + str(garnish) 374 375 # --- 376 self.LogMsg(' ...done.') 377 pass 378 379 def __call_paging_program__(self): 380 self.LOG.close() 381 os.system('less ' + info['PYLOG_PATHFILE']) 382 pass 383 384 def __call_post__(self): 385 """What to do at end of __call__.""" 386 387 self.LogMsg('') 388 389 # Store pickle. 390 self.pickle(['WRITE', info]) 391 392 # Print combinations of: 393 # - SITE/LOCAL 394 # - before/current 395 # and number of items in each. 396 # 397 self.LogMsg('') 398 for x in self.DIR_: 399 y_keys = info[x].keys() 400 y_keys.sort() 401 for y in y_keys: 402 spam = x + ': ' + y + ': ' + str(len(info[x][y])) 403 self.LogMsg(spam) 404 pass 405 self.LogMsg('--') 406 pass 407 408 self.LogMsg('') 409 self.LogMsg("Summing bytes stored on internet...") 410 info['BYTES_ON_INTERNET'] = self.reap_bytes(info['DIR_SITE_current']) 411 self.LogMsg("Total bytes stored by your ISP: " + 412 str(info['BYTES_ON_INTERNET'])) 413 self.LogMsg('* * *') 414 415 # --- THE END. 416 # If executed within Emacs, less output goes to '*Python Output*'. 417 self.__call_paging_program__() 418 419 # --- 420 pass 421 422 def normalize_file_names(self): 423 """Translate user-specified files into actually-existing files.""" 424 425 self.LogMsg('Normalizing file names...') 426 self.LogMsg('') 427 428 # (1 of 5): Expand '~' to home directory. 429 for i in info['Here2There'].keys(): 430 if i[0] == '~': 431 new_i = os.path.expanduser(i) 432 if new_i in info['Here2There'].keys(): 433 raise 'Key already exists: ' + new_i 434 pass 435 info['Here2There'][new_i] = info['Here2There'][i] 436 del info['Here2There'][i] 437 if info['DEBUG_NAMES']: 438 self.LogMsg(' here 111> ' + i + ' --> ' + new_i) 439 pass 440 pass 441 pass 442 self.debug_names_section_footer() 443 444 # (2 of 5): Ensure path begins with "/" 445 for i in info['Here2There'].keys(): 446 if not i[0] == '/': 447 new_i = os.path.join(info['WORK_DIR'], i) 448 info['Here2There'][new_i] = info['Here2There'][i] 449 del info['Here2There'][i] 450 if info['DEBUG_NAMES']: 451 msg = ' here 222> ' + i + ' --> ' 452 self.LogMsg(string.ljust(msg, 25) + new_i) 453 pass 454 pass 455 pass 456 self.debug_names_section_footer() 457 458 # (3 of 5): Convert directory name to list of HTML files. 459 for i in info['Here2There'].keys(): 460 if string.strip(info['Here2There'][i]) == '.': 461 if os.path.isdir(i): 462 dict = self.listl_dict(self.listl(i)) 463 if info['DEBUG_NAMES']: 464 msg = ' here 333> EXPANDING TO FILE LIST: ' + i 465 self.LogMsg('\n' + msg) 466 for j in dict.keys(): 467 # What if two files in same place w/ .xml and .html? 468 # 469 match_obj_ht = info['re']['html_ext'].search(j) 470 match_obj_x = info['re']['xml_ext'].search(j) 471 here = os.path.join(i, j) 472 if match_obj_ht: 473 there = '=' 474 pass 475 elif match_obj_x: 476 there = j[:-len(match_obj_x.group(0))+1] + \ info['HTML'] 478 pass 479 if match_obj_ht or match_obj_x: 480 info['Here2There'][here] = there 481 if info['DEBUG_NAMES']: 482 msg = ' 333> ' + here + ' == ' + there 483 self.LogMsg(msg) 484 pass 485 pass 486 pass 487 del info['Here2There'][i] 488 pass 489 else: 490 raise 'Not a directory: ' + i 491 pass 492 pass 493 self.debug_names_section_footer() 494 495 # (4 of 5): Change '=' to filename. 496 for i in info['Here2There'].keys(): 497 j = string.strip(info['Here2There'][i]) 498 if j == '=': 499 old_j = j 500 j = os.path.basename(i) 501 info['Here2There'][i] = j 502 if info['DEBUG_NAMES']: 503 msg = 'there 444> ' + old_j + ' --> ' + j 504 self.LogMsg(string.ljust(msg, 28) + ' '*5 + \ 'in: ' + os.path.dirname(i)) 506 pass 507 pass 508 pass 509 self.debug_names_section_footer() 510 511 # (5 of 5): Add path to there if missing 512 for i in info['Here2There'].keys(): 513 j = info['Here2There'][i] 514 if string.find(j, '/') == -1: 515 old_j = j 516 j = os.path.join(self.relative_path(i, 517 [info['WORK_DIR'], 518 info['HOME_DIR']]), j) 519 # 520 # if not j[0] == '.': j = os.path.join('.', j) 521 if j[:2] == './': j = j[2:] 522 info['Here2There'][i] = j 523 if info['DEBUG_NAMES']: 524 msg = 'there 555> ' + old_j + ' --> ' 525 self.LogMsg(string.ljust(msg, 34) + j) 526 pass 527 pass 528 pass 529 self.debug_names_section_footer() 530 531 # 888: Final display. 532 if info['DEBUG_NAMES']: 533 keys = info['Here2There'].keys() 534 keys.sort() 535 for i in keys: 536 msg = '888> ' + i 537 self.LogMsg(string.ljust(msg, 40) + \ ' --> ' + info['Here2There'][i]) 539 pass 540 pass 541 self.debug_names_section_footer() 542 543 # Create DIRS for SITE and LOCAL 544 info['DIRS_LOCAL'] = [] 545 keys = info['Here2There'].keys() 546 keys.sort() 547 if info['DEBUG_NAMES']: self.LogMsg('') 548 for i in keys: 549 j = self.relative_path(i, [info['HOME_DIR']]) 550 if not j in info['DIRS_LOCAL']: 551 info['DIRS_LOCAL'].append(j) 552 if info['DEBUG_NAMES']: 553 msg = ' DIRS_LOCAL> ' + j 554 self.LogMsg(string.ljust(msg, 40) + ' ' + i) 555 pass 556 pass 557 self.debug_names_section_footer() 558 # 559 info['DIRS_SITE'] = [] 560 keys = info['Here2There'].keys() 561 keys.sort() 562 if info['DEBUG_NAMES']: self.LogMsg('') 563 for i in keys: 564 j = self.relative_path(info['Here2There'][i], 565 [info['WORK_DIR'], 566 info['HOME_DIR']]) 567 if not j in info['DIRS_SITE']: 568 info['DIRS_SITE'].append(j) 569 if info['DEBUG_NAMES']: 570 msg = ' DIRS_SITE> ' + j 571 self.LogMsg(string.ljust(msg, 40) + ' ' + i) 572 pass 573 pass 574 self.debug_names_section_footer() 575 if info['DEBUG_NAMES']: self.LogMsg('') 576 577 pass 578 579 def __call__(self): 580 """Default attribute when no attribute used after class instance.""" 581 582 # Prior to establishing connection. 583 self.LogMsg('') 584 self.LogMsg(self.month_day_year) 585 self.LogMsg(info['PY_PATHFILE']) 586 self.LogMsg('') 587 588 # Convert user-specified file names into ones that actually-exist. 589 self.normalize_file_names() 590 591 # Prepare to connect 592 self.session_prepare() 593 594 # Make FTP connection. 595 self.session_create() 596 597 # Read existing pickle. 598 self.pickle(['TRANSFER']) 599 600 # Directory listings. 601 self.session_listings() 602 603 # Upload! 604 self.upload() 605 606 # Quit or close FTP connection. 607 self.session_quit() 608 609 # Last step of __call__. 610 self.__call_post__() 611 612 # --- 613 pass 614 615 def lookup_edb(self, dictionary): 616 """Get info from tab-delimited files w/ EDB entry form.""" 617 618 # Where to look-up password: 619 db = 'private' 620 fmt = os.path.join(os.path.expanduser('~/Db/'), db + '.fmt') 621 dat = os.path.join(os.path.expanduser('~/Db/'), db + '.dat') 622 623 # Get names of fields in private.dat 624 private_field_names = self.db_user_def.edb_field_names(fmt) 625 # ...gives: 626 # {7: 'verify-date', 6: 'notes', 5: 'secret-stuff', 4: 'why', 627 # 3: 'where', 2: 'when', 1: 'what', 0: 'who'} 628 629 # Return string. 630 return self.db_user_def.get_tab_data('secret-stuff', 631 private_field_names, 632 dictionary, 633 dat) 634 635 def reap_bytes(self, DIR_dict): 636 """Count number of bytes in DIR dictionary.""" 637 638 bytes = 0 639 for i in DIR_dict.keys(): 640 for j in DIR_dict[i].keys(): 641 n = int(DIR_dict[i][j][info['DIR_byte_position']]) 642 bytes = bytes + n 643 pass 644 pass 645 646 # --- 647 return bytes 648 649 def session_prepare(self): 650 """What to do before creating FTP session.""" 651 652 # Keep track of subdirectories on site in a sequences that can 653 # be quickly examined. 654 info['SITE_DIRS'] = [] 655 656 if (not os.path.isfile(info['PICKLE_PATHFILE']) and 657 info['DEBUG']): 658 raise 'Cannot execute in debug mode if pickle does not exist.' 659 pass 660 661 # Expressions. 662 self.capsule_expressions() 663 664 # XML substitutions. 665 self.capsule_xml_subs() 666 667 # Always upload index. 668 pathfile = os.path.expanduser(info['INDEX_LOC']) 669 self.LogMsg('Touching ' + pathfile + '...') 670 self.LogMsg('') 671 exec_string = 'touch ' + pathfile 672 os.system(exec_string) 673 674 # --- 675 pass 676 677 def session_create(self): 678 # Look-up the password: 679 site = info['SYSTEM_NAME'] 680 user = info['USER_NAME'] 681 682 # Secret stuff: 683 passwords = {} 684 passwords[(site, user)] = self.lookup_edb( 685 {'where': site, 'who': user}) 686 687 # Make connection. 688 if not info['DEBUG']: 689 try: 690 self.session.getwelcome() 691 except: 692 self.LogMsg('Opening session...') 693 self.LogMsg(' SITE: ' + site) 694 self.LogMsg(' USER: ' + user) 695 self.LogMsg('') 696 # 697 self.LogMsg(' ftplib.FTP(...)') 698 self.session = ftplib.FTP(site) 699 # 700 self.LogMsg(' self.session.login(...)') 701 self.session.login(user, passwords[(site, user)]) 702 self.LogMsg('') 703 704 # If this is first iteration, pretend that current contents 705 # of Web site are same as past contents. 706 707 if not os.path.isfile(info['PICKLE_PATHFILE']): 708 # Setting ITERATION to -1 will cause directory listing to 709 # happen when FTP connection created (no debug mode). 710 info['ITERATION'] = -1 711 712 # Get listings from Web site and local computer. 713 self.session_listings() 714 715 self.pickle(['WRITE', info]) 716 self.LogMsg('New pickle started (first iteration).') 717 pass 718 719 pass 720 pass 721 else: 722 self.LogMsg('NOT OPENING an FTP session... DEBUG MODE!)\n ') 723 pass 724 725 # --- 726 pass 727 728 def relative_path(self, path, leaders_list): 729 """Return relative path beginning w/ first subdirectory.""" 730 mystring = os.path.dirname(path) 731 732 # MUST look for longer paths before shorter ones. 733 # Any string in leaders_list will potentially be removed. 734 lengths = {} 735 for i in leaders_list: 736 lengths[len(i)] = i 737 pass 738 keys = lengths.keys() 739 keys.reverse() 740 for i in keys: 741 j = lengths[i] 742 if mystring[:len(j)] == j: 743 mystring = mystring[len(j):] 744 pass 745 if mystring == '': mystring = './' 746 pass 747 if mystring[0] == '/': mystring = mystring[1:] 748 if mystring[:2] == './': mystring = mystring[2:] 749 if mystring == '': mystring = '.' 750 return mystring 751 752 def branches(self, mylist): 753 """Return same list with branches expanded.""" 754 755 self.LogMsg('Expanding branches...') 756 757 new_list = [] 758 for item in mylist: 759 parts = string.split(item, '/') 760 for i in range(len(parts)): 761 spam = '' 762 for j in range(i): 763 spam = os.path.join(spam, parts[j]) 764 new_list.append(spam) 765 pass 766 pass 767 pass 768 769 # --- 770 return new_list 771 772 def mkdir_site(self, dirnms): 773 """Make subdirectories on Web.""" 774 775 candidates = [] 776 for dirnm in dirnms: 777 parts = string.split(dirnm, '/') 778 candidate = '' 779 for i in range(len(parts)): 780 candidate = os.path.join(candidate, parts[i]) 781 candidates.append(candidate) 782 pass 783 pass 784 candidates.sort() 785 for dirnm in candidates: 786 if not dirnm in info['SITE_DIRS']: 787 self.LogMsg(" ...creating directory: " + dirnm) 788 self.session.mkd(dirnm) 789 # Execution halts if .mkd not successful. 790 info['SITE_DIRS'].append(dirnm) 791 pass 792 pass 793 # --- 794 pass 795 796 def listing_local(self): 797 """Get listing from local computer (including random files).""" 798 799 self.LogMsg('') 800 self.LogMsg('Getting listing from local computer...') 801 dict = {} 802 dirnms = info['DIRS_LOCAL'] 803 dirnms.sort() 804 for dirnm in dirnms: 805 dir_j = os.path.join(info['HOME_DIR'], dirnm) 806 self.LogMsg(' ... ' + dir_j) 807 if os.path.isdir(dir_j): 808 dir_lines = self.listl(dir_j) 809 dir_lines_parsed = self.listl_dict(dir_lines) 810 dict[dirnm] = dir_lines_parsed 811 pass 812 else: 813 raise 'Directory not found: ' + dir_j 814 pass 815 816 self.LogMsg('') 817 return dict 818 819 def listing_site(self, action, dirnm): 820 """Get listing from Web.""" 821 822 if action == '__call__': 823 dict = {} 824 825 # Start with parent directory (ignore dirnm in __call__). 826 path = '.' 827 dict[path] = self.listing_site('__dir__', path) 828 829 for n in range(info['DIR_SITE_recurse_depth']): 830 # 5: [0, 1, 2, 3, 4] 831 paths = info['SITE_DIRS'] 832 paths.sort() 833 for path in paths: 834 parts = string.split(path, '/') 835 if len(parts) == n: 836 self.LogMsg('Getting listing from Web... ' + path) 837 # 838 # This next line may make additions to 'SITE_DIRS': 839 dict[path] = self.listing_site('__dir__', path) 840 pass 841 pass 842 pass 843 # Only append '.' after recursing. 844 info['SITE_DIRS'].append('.') 845 info['SITE_DIRS'].sort() 846 847 # Do not assume subdirectories exist on Web. 848 # Create new subdirectories as needed. 849 self.mkdir_site(info['DIRS_SITE']) 850 851 pass 852 # 853 elif action == '__dir__': 854 dir_lines = [] 855 # Output from .dir attribute: 856 # empty directory: [] 857 # missing directory: ['ftpd: DIR: No such file or directory'] 858 # non-empty directory: ['total N', '-rw-r--r-- ...'] 859 # 860 861 self.session.dir(dirnm, dir_lines.append) 862 # ['total 18', 863 # '-rw-r--r-- 1 7695 1010 3289 Jul 18 02:27 homepage.py', 864 # '-rw-r--r-- 1 7695 1010 4209 Jul 18 03:11 index.html'] 865 # 866 dir_lines_parsed = self.listl_dict(dir_lines) 867 868 # This is crucial to the "for... range" loop in '__call__'. 869 for key in dir_lines_parsed.keys(): 870 mylist = dir_lines_parsed[key] 871 if mylist[0] == 'd': 872 spam = os.path.join(dirnm, mylist[-1]) 873 if dirnm == '.': 874 # Do not include leading '.' 875 spam = mylist[-1] 876 pass 877 # 878 info['SITE_DIRS'].append(spam) 879 pass 880 pass 881 return dir_lines_parsed 882 # 883 else: 884 raise 'Action not recognized: ' + action 885 # 886 return dict 887 888 def session_listings(self): 889 """Current directory listings.""" 890 891 # 1 of 2: LOCAL. 892 info['DIR_LOCAL_current'] = self.listing_local() 893 894 # 2 of 2: SITE. 895 # 896 # Debug? 897 if info['DEBUG']: 898 # Make a copy of before! 899 # This assumes nothing changed on SITE. 900 # This also happened when ITERATION was 0. 901 info['DIR_SITE_current'] = info['DIR_SITE_past'] 902 pass 903 else: 904 info['DIR_SITE_current'] = self.listing_site('__call__', None) 905 # 906 self.LogMsg("Summing bytes stored on internet...") 907 bytes = self.reap_bytes(info['DIR_SITE_current']) 908 info['BYTES_ON_INTERNET'] = bytes 909 self.LogMsg("Total bytes stored by your ISP: " + 910 str(info['BYTES_ON_INTERNET'])) 911 pass 912 913 # --- 914 pass 915 916 def stor2DIR_SITEkey(self, my_arg): 917 """Convert from STOR format to DIR_.key() format.""" 918 919 # Rules: 920 # If just '.' keep it ('.' returns '.'). 921 # If more than just '.' then remove it ('./A/B/c.txt' returns 'A/B'). 922 my_arg = my_arg[0:string.rfind(my_arg, '/')] 923 if (not my_arg == '.' and 924 my_arg[0] == '.' 925 ): 926 my_arg = my_arg[2:] 927 pass 928 929 # ----- 930 return my_arg 931 932 def upload(self): 933 """Start of upload process.""" 934 # 1. file in Here2There not on site, upload it (by file). 935 # 2. file modified on local computer, upload it. 936 937 self.LogMsg('\n\n') 938 self.upload_H2T() 939 self.LogMsg('\n\n') 940 self.upload_MOD() 941 942 # --- 943 pass 944 945 def get_listl_info(self, dir_listing_name, dirnm, filename): 946 """Return None or values from directory listing""" 947 return_object = None 948 if info[dir_listing_name].has_key(dirnm): 949 if info[dir_listing_name][dirnm].has_key(filename): 950 return_object = info[dir_listing_name][dirnm][filename] 951 pass 952 pass 953 return return_object 954 955 def upload_H2T(self): 956 # 1. 957 # Upload if in Here2There but not in DIR_SITE_current. 958 # 959 info['H2T_on_the_fly'] = [] 960 961 keys = info['Here2There'].keys() 962 keys.sort() 963 dirs_seen = [] 964 for key in keys: 965 key_local = key 966 key_site = info['Here2There'][key] 967 key_local_nopath = string.split(key_local, '/')[-1] 968 key_site_nopath = string.split(key_site, '/')[-1] 969 970 # Trigger is "upload_switch". 971 upload_switch = None 972 973 dir_site = self.relative_path(key_site, 974 [info['WORK_DIR'], 975 info['HOME_DIR']]) 976 if not dir_site in dirs_seen: 977 self.LogMsg('') 978 self.LogMsg('CHECKING FOR Here2There NOT ON INTERNET: ' + \ dir_site) 980 dirs_seen.append(dir_site) 981 pass 982 983 if not info['DIR_SITE_current'].has_key( 984 dir_site): 985 upload_switch = 't' 986 if info['DEBUG']: 987 spam = dir_site + ' not in DIR_SITE_current!' 988 self.LogMsg(' =-=-=-> ' + spam) 989 pass 990 pass 991 elif not info['DIR_SITE_current'][dir_site].has_key( 992 key_site_nopath): 993 upload_switch = 't' 994 if info['DEBUG']: 995 spam = key_site_nopath + ' not in DIR_SITE_current[' 996 spam = spam + dir_site + '] !' 997 self.LogMsg(' =-=-=-> ' + spam) 998 pass 999 pass 1000 1001 if upload_switch: 1002 # Sometimes the file doesn't exist, for example 1003 # the HTML version of a Python script only exists 1004 # just prior to upload. 1005 info_now = None 1006 dir_local = self.relative_path(key_local, 1007 [info['HOME_DIR']]) 1008 1009 spam_keys = info['DIR_LOCAL_current'][dir_local].keys() 1010 if key_local_nopath in spam_keys: 1011 info_now = info['DIR_LOCAL_current'][dir_local][ 1012 key_local_nopath] 1013 pass 1014 1015 info['H2T_on_the_fly'].append(key_local) 1016 self.on_the_fly(key_local, key_site, info_now) 1017 pass 1018 pass 1019 pass 1020 1021 def upload_MOD(self): 1022 # 2. 1023 # Look for files that have been modified. 1024 # 1025 Here2There_keys = info['Here2There'].keys() 1026 dirnms = info['DIRS_LOCAL'] 1027 dirnms.sort() 1028 for dir_local in dirnms: 1029 self.LogMsg('') 1030 self.LogMsg("CHECKING FOR MODIFIED FILES ON LOCAL: " + \ dir_local) 1032 1033 filenames = info['DIR_LOCAL_current'][dir_local].keys() 1034 filenames.sort() 1035 for filename in filenames: 1036 1037 # This part is sensitive; must be a string that will 1038 # match exactly... no spurious '././' in pathfilename! 1039 # 1040 pathfilename = os.path.join(info['HOME_DIR'], 1041 dir_local, 1042 filename) 1043 if pathfilename in Here2There_keys: 1044 key_local = pathfilename 1045 key_site = info['Here2There'][pathfilename] 1046 1047 # Trigger is "upload_switch". 1048 upload_switch = None 1049 1050 # Now and then. 1051 info_past = self.get_listl_info('DIR_LOCAL_past', 1052 dir_local, filename) 1053 info_now = self.get_listl_info('DIR_LOCAL_current', 1054 dir_local, filename) 1055 1056 # Upload modified files. 1057 if (info_past == None or 1058 info_now == None): 1059 upload_switch = 't' 1060 pass 1061 elif not info_past == info_now: 1062 upload_switch = 't' 1063 pass 1064 1065 if upload_switch: 1066 if not key_local in info['H2T_on_the_fly']: 1067 self.LogMsg('') 1068 self.LogMsg(' PAST> ' + str(info_past)) 1069 self.LogMsg(' NOW> ' + str(info_now)) 1070 self.on_the_fly(key_local, key_site, info_now) 1071 pass 1072 pass 1073 pass 1074 pass 1075 pass 1076 # --- 1077 pass 1078 1079 def on_the_fly(self, key_local, key_site, info_now): 1080 """Very long attribute; calls modifications attribute.""" 1081 1082 self.LogMsg(' on_the_fly: ' + key_local + ' --> ' + key_site) 1083 1084 if info['DEBUG']: 1085 if info_now == None: 1086 self.LogMsg(' !! File has not been created yet. !! ') 1087 pass 1088 else: 1089 self.LogMsg(str(info_now)) 1090 pass 1091 pass 1092 1093 if info_now == None: 1094 bytes = -1 1095 pass 1096 else: 1097 bytes = int(info_now[info['DIR_byte_position']]) 1098 pass 1099 # 1100 # Put '#' in front of 'and' to exclude this script. 1101 spam = string.split(key_local, '/')[-1] 1102 if (bytes > info['MAX_BYTES'] 1103 and not spam in info['MAX_BYTES_EXCEPTIONS'] 1104 ): 1105 self.LogMsg(' '*9 + 'NOT uploaded (too large): bytes: ' + 1106 str(bytes)) 1107 return 1108 1109 # In addition, to "upload_switch", variable "new_file" is a trigger 1110 # also. Value of "file2up" can be changed by "new_file". 1111 # 1112 new_file = None 1113 del_f2u = None 1114 file2up = key_local 1115 1116 filename = os.path.basename(file2up) 1117 # ----------------------------------------------------- 1118 # Last-second changes to file before upload. 1119 # 1120 # 1. EXTENSION = .py 1121 # 1122 if file2up[-3:] == '.py': 1123 file2up, new_file, del_f2u = self.modifications(key_site, 1124 file2up, 1125 'py') 1126 pass 1127 # 1128 # 2. EXTENSION = .xml 1129 # Files ending in '.xml' (instead of using 1130 # jade to create .html) that are not stored as raw XML. 1131 # 1132 elif (file2up[-4:] == '.xml' and \ not key_site[-4:] == '.xml'): 1134 file2up, new_file, del_f2u = self.modifications(key_site, 1135 file2up, 1136 'xml') 1137 pass 1138 # 1139 # 3. EXTENSION = .xml 1140 # Files ending in '.xml' that will be stored as raw XML. 1141 elif (file2up[-4:] == '.xml' and \ key_site[-4:] == '.xml'): 1143 file2up = file2up 1144 pass 1145 # 1146 # 4. EXTENSION = '.el' or '.txt' or ... to HTML. 1147 # Files ending in '.el' or '.txt' or ... to HTML. 1148 # 1149 elif ('.' + string.split(file2up, '.')[-1] 1150 in info['EXTENSIONS_TXT2HTML'] 1151 ) and key_site[-5:] == '.' + info['HTML']: 1152 # 1153 file2up, new_file, del_f2u = self.modifications(key_site, 1154 file2up, 1155 'txt2html') 1156 pass 1157 # 1158 # 999. RANDOM FILES 1159 # Examples are ".emacs" and ".signature" and... 1160 # 1161 elif (filename[0] == '.' or 1162 filename == string.split(info['PYLOG_PATHFILE_OLD'], '/')[-1] 1163 ): 1164 file2up, new_file, del_f2u = self.modifications(key_site, 1165 file2up, 1166 'dot_file') 1167 pass 1168 # -------------------------------------------- 1169 1170 # Variable 'new_file' contains modifications. 1171 if not new_file == None: 1172 # Usually 'file2up' is in ~/Db/Homepage and is just 1173 # file name (no path); in this case it is a temporary file 1174 # and has a path; see up_file too. 1175 # 1176 file2up = tempfile.mktemp() 1177 spam = open(file2up, 'w') 1178 spam.write(new_file) 1179 spam.close() 1180 new_file = None 1181 pass 1182 1183 # Finally! Time to upload... 1184 self.up_file(file2up, key_site) 1185 1186 if del_f2u: 1187 exec_string = string.join(["rm -f", file2up]) 1188 self.LogMsg(" ... " + exec_string) 1189 # 1190 if not info['DEBUG']: 1191 os.system(exec_string) 1192 pass 1193 pass 1194 1195 new_file = None 1196 del_f2u = None 1197 file2up = None 1198 1199 # --- 1200 pass 1201 1202 def modifications(self, key_site, file2up, type): 1203 """Make changes.""" 1204 1205 new_file = None 1206 del_f2u = None # Flag for deleting uploaded file. 1207 1208 if type == 'py': 1209 exec_string = string.join(['python', 1210 info['PY2HTML'] 1211 ]) 1212 if info['PY2HTML_OPTIONS']: 1213 exec_string = string.join([exec_string, 1214 info['PY2HTML_OPTIONS'] 1215 ]) 1216 pass 1217 1218 exec_string = string.join([exec_string, 1219 file2up]) 1220 self.LogMsg(" ..... " + exec_string) 1221 # 1222 if not info['DEBUG']: 1223 os.system(exec_string) 1224 pass 1225 file2up = file2up + '.' + info['HTML'] 1226 del_f2u = 't' 1227 # 1228 pass 1229 # 1230 elif type == 'xml': 1231 # Convert general entities to numeric entities. 1232 try: 1233 type(num_ents) 1234 except: 1235 import num_ents 1236 myNumCharRef = num_ents.NumCharRef() 1237 pass 1238 # 1239 if not info['DEBUG']: 1240 new_file = myNumCharRef( 1241 file2up, 1242 '/usr/share/sgml/html/dtd/xml/1.0/xhtml.soc') 1243 1244 # XML substitutions. 1245 match_obj = info['re']['placeholder'].search(new_file) 1246 while not match_obj == None: 1247 new_file = pre.sub(match_obj.group(0), 1248 self.xml_subs[match_obj.group(1)], 1249 new_file) 1250 # 1251 # Do not need to check for unsuccessful substitutions 1252 # (KeyError will happen if placeholder is invalid). 1253 match_obj = info['re']['placeholder'].search(new_file) 1254 pass 1255 1256 # .xml --> .html changes (regarding XHTML). 1257 # Delete <?xml?> declaration. 1258 if key_site[-5:] == '.' + info['HTML']: 1259 # 1260 # If DOCTYPE is html, just remove <?xml?>, otherwise 1261 # wrap whole thing in <pre></pre>. 1262 # 1263 spam = string.find(new_file, '<!DOCTYPE') 1264 if spam > -1: 1265 spam = string.split(new_file[spam:])[1] 1266 if string.upper(spam) == 'HTML': 1267 # 1268 # Can comments appear before doctype? 1269 spam = '<!-- XML declaration removed by \n' 1270 spam = spam + ' '*17 + info['WEB_HOME'] 1271 spam = spam + info['PYNAME_HTML'] 1272 spam = spam + ' -->\n' 1273 spam = '' 1274 new_file = pre.sub('^ *<\?xml[^>]*>', 1275 spam, new_file) 1276 pass 1277 pass 1278 else: 1279 new_file = self.read_file_return_with_pre(file2up) 1280 pass 1281 pass 1282 pass 1283 pass 1284 # 1285 elif (type == 'txt2html' or 1286 type == 'dot_file' or 1287 type == 'pre_file'): 1288 new_file = self.read_file_return_with_pre(file2up) 1289 pass 1290 # 1291 else: 1292 raise 'Unknown type: ' + type 1293 1294 # If "new_file" is not empty, it contains contents to be 1295 # uploaded. Contents will need to be saved to a file, thus 1296 # "file2up" will be changed by calling attribute. 1297 1298 # ----- 1299 return file2up, new_file, del_f2u 1300 1301 def up_file(self, file2up, key_site): 1302 """Upload file.""" 1303 1304 stor_command = string.join(['STOR', key_site]) 1305 1306 if not info['DEBUG']: 1307 # 1308 if (not os.path.isfile(file2up)): 1309 self.LogMsg(' ' + 'FILE NOT FOUND.') 1310 pass 1311 else: 1312 # UPLOAD FILE: 1313 local_object = open(file2up, 'r') 1314 self.LogMsg(' ' + stor_command) 1315 self.session.storbinary(stor_command, 1316 local_object, 1317 8*1024) 1318 local_object.close() 1319 # 1320 # Track subdirectories to update DIR_SITE_current later. 1321 key = self.stor2DIR_SITEkey(key_site) 1322 if not key in info['DIR_SITE_received_upload']: 1323 info['DIR_SITE_received_upload'].append(key) 1324 pass 1325 # 1326 pass 1327 pass 1328 else: 1329 self.LogMsg(' '*30 + ' ... DEBUG, not uploaded.') 1330 pass 1331 1332 # --- 1333 pass 1334 1335 def session_quit(self): 1336 """Quit FTP session.""" 1337 1338 self.LogMsg('') 1339 self.LogMsg("PREPARING TO CLOSE CONNECTION...") 1340 1341 # Get fresh listings from certain subdirectories. 1342 for path in info['DIR_SITE_received_upload']: 1343 info['DIR_SITE_current'][path] = self.listing_site( 1344 '__dir__', path) 1345 pass 1346 1347 if not info['DEBUG']: 1348 self.session.quit() 1349 pass 1350 1351 # --- 1352 pass 1353 1354 def read_file_return_with_pre(self, file_name): 1355 """Surround plain text file with HTML markup.""" 1356 1357 # Simply return "...<pre>", contents of file, and "</pre>...". 1358 spam = open(file_name, 'r') 1359 contents = spam.read() 1360 spam.close() 1361 1362 # Not so simple. Do minimal conversion of character references. 1363 # (sect. 2.4 in www.w3.org/TR/1998/REC-xml-19980210). 1364 # Order counts! 1365 contents = string.replace(contents, '&', '&') 1366 contents = string.replace(contents, '<', '<') 1367 contents = string.replace(contents, '>', '>') 1368 1369 # ----- 1370 return self.html_pre_open + contents + self.html_pre_close 1371 1372 def capsule_expressions(self): 1373 """Regular expressions.""" 1374 1375 self.re_Subdirectory = pre.compile('^d') 1376 self.re_XHTmlFile = pre.compile('(x|ht)ml$', pre.I) 1377 1378 # --- 1379 pass 1380 1381 def capsule_xml_subs_final(self): 1382 """SEE: capsule_xml_subs().""" 1383 for xs0 in self.xml_subs.keys(): 1384 for xs1 in self.xml_subs.keys(): 1385 self.xml_subs[xs0] = pre.sub('__' + xs1 + '__', 1386 self.xml_subs[xs1], 1387 self.xml_subs[xs0]) 1388 pass 1389 pass 1390 pass 1391 1392 # NOTE: 1393 # This def is LAST because something about """quoting""" causes 1394 # Emacs indent-for-tab-command to break. 1395 def capsule_xml_subs(self): 1396 """General substitutions that probably belong outside this script.""" 1397 1398 # All xml_subs keys are lowercase, except "Google". May like to 1399 # check for (and reject) all uppercase keys. 1400 # ----- 1401 1402 # <PRE> 1403 self.html_pre_open = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html> <head> <title></title> </head> <body> <pre text=""" + '__body_attrs__' + ">" 1410 1411 # </PRE> 1412 self.html_pre_close = """\ </pre> </body> </html>""" 1416 1417 1418 # To trigger substitution, put key in .xml surrounded by 1419 # double underscores. WARNING: key will be used as part of a 1420 # pattern used in pre.sub(). 1421 1422 self.xml_subs = {} 1423 1424 # Assume ``[A-Z_]+'' keys from info are xml_subs: 1425 for key in info.keys(): 1426 if not key == None: 1427 if pre.compile('^[A-Z_]+$').match(key): 1428 if type(info[key]) == types.StringType: 1429 self.xml_subs[key] = info[key] 1430 pass 1431 pass 1432 pass 1433 pass 1434 1435 # Replace __placeholder__ with HTML names of script and script's log. 1436 self.xml_subs['pyname'] = info['PYNAME'] 1437 self.xml_subs['pydata_html'] = info['PYDATA_HTML'] 1438 self.xml_subs['pylog_html'] = info['PYLOG_HTML'] 1439 self.xml_subs['pyname_html'] = info['PYNAME_HTML'] 1440 # 1441 # Example from index.xml ... 1442 # <a href="__PYLOG_HTML__" shape="rect">log file</a>). 1443 1444 1445 # Replace __placeholder__ with non-breaking spaces. 1446 self.xml_subs['nbsps'] = ' ' 1447 1448 # Replace __placeholder__ with my email address, as an anchor start. 1449 self.xml_subs['mailto_me_href'] = info['MAILTO_ME_HREF'] 1450 self.xml_subs['mailto_me_href_addr'] = info['MAILTO_ME_HREF_ADDR'] 1451 1452 # Replace __placeholder__ with my GnuPG user ID. 1453 self.xml_subs['pgp_userid'] = info['PGP_USERID'] 1454 1455 # Replace __placeholder__ with current date. 1456 self.xml_subs['month_day_year'] = self.month_day_year 1457 1458 # Replace __placeholder__ with home page anchor. 1459 spam = """<a href="index.html" shape="rect"><b>Home</b></a>""" 1460 self.xml_subs['home'] = spam 1461 1462 # Replace __placeholder__ with body-tag attributes. 1463 # Other colors: <body bgcolor=#faebd7><!-- antique white --> 1464 # 1465 # This goes within 'text="__body_attrs__" ! 1466 spam = '#000000" bgcolor="' + '__BGCOLOR__' + """\" link="#0000FF" vlink="#800080" alink="#FF0000""" 1468 self.xml_subs['body_attrs'] = spam 1469 1470 # Replace __placeholder__ with Google anchor. 1471 # Had to escape "#" inside string. 1472 spam = "<a href=\"" + info['GOOGLE_URL'] 1473 spam = spam + """\" shape="rect">""" 1474 self.xml_subs['Google'] = spam + """ <font color="#0039b6">G</font> <font color="#c41200">o</font> <font color="#f3c518">o</font> <font color="#0039b6">g</font> <font color="#30a72f">l</font> <font color="#c41200">e</font> </a>""" 1482 1483 1484 # Replace __placeholder__ with div.banner of hyperlinks. 1485 # Note this in <body><head>: 1486 # <link href="generic.css" type="text/css" rel="stylesheet"/> 1487 # 1488 # DO NOT USE "—" (Netscape 4.08 does not convert to "--"). 1489 # 1490 self.xml_subs['div_banner_hyperlinks'] = """<div class="banner"> __home__ - __Google__ - <a href="hyplheal.html" shape="rect"><small>Health</small></a> - <a href="hyplpers.html" shape="rect"><small>Personal</small></a> - <a href="hyplpoli.html" shape="rect"><small>Political</small></a> - <a href="hypltech.html" shape="rect"><small>Technical</small></a> </div> """ 1502 1503 # Replace __placeholder__ with div.banner of just two hyperlinks. 1504 # 1505 self.xml_subs['div_banner_hyperlinks_brief'] = """<div class="banner"> __home__ - __Google__ </div> """ 1510 1511 # Replace __placeholder__ with a paragraph with links to this 1512 # script and it's control and log files. 1513 # 1514 self.xml_subs['script_description_p'] = """ <p align="center"> <small>Updated with <a href="__PYNAME_HTML_ABSOLUTE__" shape="rect">a Python script</a> (on __month_day_year__). <br clear="none"/> Latest <a href="homeptab.html" shape="rect">tab-delimited control file</a>. <br clear="none"/> Previous <a href="homep_lg.html" shape="rect">log file</a>. </small></p> """ 1529 1530 # Replace __placeholder__ with div.navfooter stuff. 1531 anchor = ' <a href="' + info['MAILTO_ME_HREF'] 1532 anchor = anchor + '" shape="rect">' + info['MAILTO_ME_NAME'] + '</a>' 1533 # 1534 self.xml_subs['div_navfooter'] = """ <hr class="hide"/> <div class="NAVFOOTER"> <!-- To show your readers that you have taken the care to create an interoperable Web page, you may display this icon on any page that validates. --> <a href="http://validator.w3.org/check/referer" shape="rect"> <img src="http://www.w3.org/Icons/valid-xhtml10" alt="Valid XHTML 1.0!" height="31" width="88" align="right" border="0" /> </a> <a href="http://validator.w3.org/check/referer" shape="rect">Click here (or click icon) to validate markup. </a> <br clear="none" /> http://  <a href="http://""" + info['SYSTEM_NAME'] + """\" shape="rect"> """ + info['SYSTEM_NAME'] + """ </a>  /  <a href=\"""" + info['INDEX_WEB'] + """\" shape="rect"> ~""" + info['USER_NAME'] + """ </a> <address> Email: """ + anchor + """ </address> </div> """ 1565 1566 # This must be after final addition to xml_subs (does single 1567 # substitution inside dictionary, no recursion). 1568 self.capsule_xml_subs_final() 1569 # --- 1570 pass 1571 1572 # === 1573 pass 1574 1575 # MAIN: 1576 if not __name__ == 'the_name_I_cannot_specify': 1577 maine = Homepage(info) 1578 maine() 1579 pass 1580 1581 # ----------------------------------------------------------------------------- 1582 # Example of generating "booksred.html" using jade. ('\x22' = '"'). 1583 # 1584 # jade -d /usr/lib/dsssl/stylesheets/docbook/html/docbook.dsl 1585 # -t sgml -o booksred.html 1586 # /usr/lib/sgml/declaration/xml.decl booksred.xml 1587 # perl -- booksred.pl booksred.html >| foo.html 1588 # mv foo.html booksred.html 1589 # 1590 # (Perl is simply to put "<BR\n>" before item number: 1591 # $/ = "<DT\n>"; 1592 # while (<>) { 1593 # if (/^[0-9]+[.]/) { print "<BR\n>$_"; } 1594 # else { print "$_"; } 1595 # } 1596 # ) 1597 # ----------------------------------------------------------------------------- 1598 # NOTE: 'jade_run' is a shell script that can reduce uploads; it 1599 # sets-aside existing .html files, executes jade, and finally 1600 # it replaces new .html with old .html if running jade didn't 1601 # change a given file; that replacement undoes jade's modification 1602 # of date/time for a given file; this script won't upload a file 1603 # if date/time did not change between executions. 1604 # 1605 # ----------------------------------------------------------------------------- 1606 # To-do: - This uploads, but doesn't download files only on web site that 1607 # may have been uploaded without storing copy on laptop. 1608 # - log file created by this script is a kind of 'site map'. 1609 # - guarantee existence of public-domain links (e.g., t4700ct.html). 1610 # - if C-c C-c in Emacs, suppress set of output from *Python Output*. 1611 # - use "--full-time" with ``ls'' on local computer. 1612 # - make sure os.path.expanduser always used. 1613 # - this auto-uploads: 1614 # A['exim_d.html'] = 'exim_d.html' 1615 # but will this auto-upload to subdirectory Image? 161 |