reindex.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. #!/usr/bin/python
  2. import re, os
  3. class Error(Exception): pass
  4. STATUSES = """DRAFT NEEDS-REVISION NEEDS-RESEARCH OPEN ACCEPTED META FINISHED
  5. CLOSED SUPERSEDED DEAD REJECTED OBSOLETE RESERVE INFORMATIONAL""".split()
  6. REQUIRED_FIELDS = [ "Filename", "Status", "Title"]
  7. CONDITIONAL_FIELDS = { "OPEN" : [ "Target", "Ticket" ],
  8. "ACCEPTED" : [ "Target", "Ticket" ],
  9. "CLOSED" : [ "Implemented-In", "Ticket" ],
  10. "FINISHED" : [ "Implemented-In", "Ticket" ] }
  11. FNAME_RE = re.compile(r'^(\d\d\d)-.*[^\~]$')
  12. DIR = "."
  13. OUTFILE = "000-index.txt"
  14. TMPFILE = OUTFILE+".tmp"
  15. def indexed(seq):
  16. n = 0
  17. for i in seq:
  18. yield n, i
  19. n += 1
  20. def readProposal(fn):
  21. fields = { }
  22. f = open(fn, 'r')
  23. lastField = None
  24. try:
  25. for lineno, line in indexed(f):
  26. line = line.rstrip()
  27. if not line:
  28. return fields
  29. if line[0].isspace():
  30. fields[lastField] += " %s"%(line.strip())
  31. else:
  32. parts = line.split(":", 1)
  33. if len(parts) != 2:
  34. raise Error("%s:%s: Neither field nor continuation"%
  35. (fn,lineno))
  36. else:
  37. fields[parts[0]] = parts[1].strip()
  38. lastField = parts[0]
  39. return fields
  40. finally:
  41. f.close()
  42. def getProposalNumber(fn):
  43. """Get the proposal's assigned number from its filename `fn`."""
  44. parts = fn.split('-', 1)
  45. assert len(parts) == 2, \
  46. "Filename must have a proposal number and title separated by a '-'"
  47. return int(parts[0])
  48. def checkProposal(fn, fields):
  49. status = fields.get("Status")
  50. need_fields = REQUIRED_FIELDS + CONDITIONAL_FIELDS.get(status, [])
  51. number = getProposalNumber(fn)
  52. # Since prop#288 was the newest when we began requiring the 'Ticket:'
  53. # field, we don't require the field for it or any older proposal.
  54. # (Although you're encouraged to add it to your proposal, and add it for
  55. # older proposals where you know the correct ticket, as it greatly helps
  56. # newcomers find more information on the implementation.)
  57. if number <= 288:
  58. if "Ticket" in need_fields:
  59. need_fields.remove("Ticket")
  60. for f in need_fields:
  61. if not fields.has_key(f):
  62. raise Error("%s has no %s field"%(fn, f))
  63. if fn != fields['Filename']:
  64. print `fn`, `fields['Filename']`
  65. raise Error("Mismatched Filename field in %s"%fn)
  66. if fields['Title'][-1] == '.':
  67. fields['Title'] = fields['Title'][:-1]
  68. status = fields['Status'] = status.upper()
  69. if status not in STATUSES:
  70. raise Error("I've never heard of status %s in %s"%(status,fn))
  71. if status in [ "SUPERSEDED", "DEAD" ]:
  72. for f in [ 'Implemented-In', 'Target' ]:
  73. if fields.has_key(f): del fields[f]
  74. def readProposals():
  75. res = []
  76. for fn in os.listdir(DIR):
  77. m = FNAME_RE.match(fn)
  78. if not m: continue
  79. if not fn.endswith(".txt"):
  80. raise Error("%s doesn't end with .txt"%fn)
  81. num = m.group(1)
  82. fields = readProposal(fn)
  83. checkProposal(fn, fields)
  84. fields['num'] = num
  85. res.append(fields)
  86. return res
  87. def writeIndexFile(proposals):
  88. proposals.sort(key=lambda f:f['num'])
  89. seenStatuses = set()
  90. for p in proposals:
  91. seenStatuses.add(p['Status'])
  92. out = open(TMPFILE, 'w')
  93. inf = open(OUTFILE, 'r')
  94. for line in inf:
  95. out.write(line)
  96. if line.startswith("====="): break
  97. inf.close()
  98. out.write("Proposals by number:\n\n")
  99. for prop in proposals:
  100. out.write("%(num)s %(Title)s [%(Status)s]\n"%prop)
  101. out.write("\n\nProposals by status:\n\n")
  102. for s in STATUSES:
  103. if s not in seenStatuses: continue
  104. out.write(" %s:\n"%s)
  105. for prop in proposals:
  106. if s == prop['Status']:
  107. out.write(" %(num)s %(Title)s"%prop)
  108. if prop.has_key('Target'):
  109. out.write(" [for %(Target)s]"%prop)
  110. if prop.has_key('Implemented-In'):
  111. out.write(" [in %(Implemented-In)s]"%prop)
  112. out.write("\n")
  113. out.close()
  114. os.rename(TMPFILE, OUTFILE)
  115. try:
  116. os.unlink(TMPFILE)
  117. except OSError:
  118. pass
  119. writeIndexFile(readProposals())