Builds as one slide per step when the template has Build 1, 2, ... (v1.3.2)
- Template.step_builds: build slides without ProPresenter's build feature;
each build in the notes becomes one slide per step from the template's
k-th build slide (last one reused): heading + first item, then one more
line per slide; font runs cut to each step's text
- Build headings (":"/"?"/italic, or above a numbered / same-word list) kept
as Line.lead; TextEdit format marks them with ">"
- Prefer a one-slide-per-step template over a newer old-style one
- Clear message when a notes file in Google Drive can't be read
- README: builds, underline, fonts are never changed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -75,12 +75,23 @@ Tools first if Python is missing). The first run asks which campus it is and sav
|
|||||||
|---|---|
|
|---|---|
|
||||||
| first line / second line / bare reference | **Title** slide (title, subtitle, main passage) |
|
| first line / second line / bare reference | **Title** slide (title, subtitle, main passage) |
|
||||||
| paragraph starting with a reference (`John 1:12 ESV …`) | **SCRIPTURE**. Bold words stay bold. |
|
| paragraph starting with a reference (`John 1:12 ESV …`) | **SCRIPTURE**. Bold words stay bold. |
|
||||||
|
| underlined words (any slide) | underlined on the slide |
|
||||||
| a line on its own | **Point** |
|
| a line on its own | **Point** |
|
||||||
| lead-in (`…:` / `…?` / italic) + one line | one **Point**, lead-in in the italic style |
|
| lead-in (`…:` / `…?` / italic) + one line | one **Point**, lead-in in the italic style |
|
||||||
| 3+ lines together, a list, or after "(one at a time)" | **\*STREAM DECK\*** slide + **Build** (one step per line) |
|
| 3+ lines together, a list, or after "(one at a time)" | a **Build**: one step per line (see below) |
|
||||||
| `Sermon in a Sentence:` + sentence | **SERMON IN A SENTENCE** |
|
| `Sermon in a Sentence:` + sentence | **SERMON IN A SENTENCE** |
|
||||||
| `(…)`, `[…]`, `// …`, coloured non-scripture notes | left off, shown in the review |
|
| `(…)`, `[…]`, `// …`, coloured non-scripture notes | left off, shown in the review |
|
||||||
|
|
||||||
|
**Builds** follow the template:
|
||||||
|
- slides **Build 1, Build 2, …** (no build feature on them): one slide per step. Step 1 is the
|
||||||
|
heading with the first item, each next slide adds one line. Step *k* is a copy of the
|
||||||
|
template's *k*-th build slide (the last one again for longer builds). The heading is a first
|
||||||
|
line ending in `:`/`?`, italic, or above a numbered / same-word list.
|
||||||
|
- one **Build** slide using ProPresenter's build feature: a **\*STREAM DECK\*** slide, then the
|
||||||
|
build with one build step per line.
|
||||||
|
|
||||||
|
With two templates of the same kind, the one-slide-per-step one is used, else the newest.
|
||||||
|
|
||||||
Formatting always comes from the template's own sample slides. To change how something looks,
|
Formatting always comes from the template's own sample slides. To change how something looks,
|
||||||
change the template in ProPresenter and the tool follows. Templates are found by name: any
|
change the template in ProPresenter and the tool follows. Templates are found by name: any
|
||||||
`_TEMPLATE … DO NOT DELETE …` presentation in Messages, with `GENERIC` marking the generic one.
|
`_TEMPLATE … DO NOT DELETE …` presentation in Messages, with `GENERIC` marking the generic one.
|
||||||
@@ -95,7 +106,8 @@ On themed screens ProPresenter uses the theme's font size, and only words styled
|
|||||||
from the rest of their box keep bold/italic.
|
from the rest of their box keep bold/italic.
|
||||||
- Scriptures too long anywhere are split at verse numbers into as few, even slides as possible,
|
- Scriptures too long anywhere are split at verse numbers into as few, even slides as possible,
|
||||||
all with the same reference.
|
all with the same reference.
|
||||||
- Other text too big for the slide is reduced in 1pt steps.
|
- Other text too big for the slide is reported with the size it would fit at; font sizes are
|
||||||
|
never changed (several outputs share them).
|
||||||
- Text too long for a theme box is flagged, since the theme fixes the size there.
|
- Text too long for a theme box is flagged, since the theme fixes the size there.
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|||||||
+20
-5
@@ -33,7 +33,7 @@ import spelling
|
|||||||
import themes
|
import themes
|
||||||
import youtube
|
import youtube
|
||||||
|
|
||||||
VERSION = "1.3.1"
|
VERSION = "1.3.2"
|
||||||
API = None # ProPresenter's network API on this Mac - set in run() from its preferences
|
API = None # ProPresenter's network API on this Mac - set in run() from its preferences
|
||||||
SUPPORT = os.path.expanduser("~/Library/Application Support/MessageNotes")
|
SUPPORT = os.path.expanduser("~/Library/Application Support/MessageNotes")
|
||||||
LOG = os.path.expanduser("~/Library/Logs/message-notes.log")
|
LOG = os.path.expanduser("~/Library/Logs/message-notes.log")
|
||||||
@@ -113,10 +113,20 @@ def find_library(explicit=None):
|
|||||||
return settings.find_library(explicit=explicit)
|
return settings.find_library(explicit=explicit)
|
||||||
|
|
||||||
|
|
||||||
|
def template_rank(path):
|
||||||
|
"""Which of two templates of the same kind to use: one that shows builds as one slide per
|
||||||
|
step (Build 1, Build 2, ...) first, then the most recently saved."""
|
||||||
|
try:
|
||||||
|
step = deck.Template(open(path, "rb").read(), path).step_builds
|
||||||
|
except Exception:
|
||||||
|
step = False
|
||||||
|
return (step, os.path.getmtime(path))
|
||||||
|
|
||||||
|
|
||||||
def find_templates(library):
|
def find_templates(library):
|
||||||
out = [os.path.join(library, f) for f in sorted(os.listdir(library)) if settings.is_template(f)]
|
out = [os.path.join(library, f) for f in sorted(os.listdir(library)) if settings.is_template(f)]
|
||||||
series = sorted([p for p in out if "generic" not in os.path.basename(p).lower()], key=os.path.getmtime, reverse=True)
|
series = sorted([p for p in out if "generic" not in os.path.basename(p).lower()], key=template_rank, reverse=True)
|
||||||
generic = sorted([p for p in out if "generic" in os.path.basename(p).lower()], key=os.path.getmtime, reverse=True)
|
generic = sorted([p for p in out if "generic" in os.path.basename(p).lower()], key=template_rank, reverse=True)
|
||||||
return series, generic
|
return series, generic
|
||||||
|
|
||||||
|
|
||||||
@@ -155,8 +165,13 @@ def read_notes(path):
|
|||||||
"""Notes file -> RTF bytes (converting Word with macOS textutil)."""
|
"""Notes file -> RTF bytes (converting Word with macOS textutil)."""
|
||||||
low = path.lower()
|
low = path.lower()
|
||||||
if low.endswith(".rtf"):
|
if low.endswith(".rtf"):
|
||||||
with open(path, "rb") as f:
|
try:
|
||||||
return f.read()
|
with open(path, "rb") as f:
|
||||||
|
return f.read()
|
||||||
|
except OSError as e:
|
||||||
|
why = "Google Drive isn't running or hasn't downloaded it - open Google Drive, or save the " \
|
||||||
|
"notes into Downloads" if "/CloudStorage/" in path else e.strerror or str(e)
|
||||||
|
raise SystemExit("Could not read %s\n (%s)" % (os.path.basename(path), why))
|
||||||
src, tmpdir = path, None
|
src, tmpdir = path, None
|
||||||
if low.endswith(".docx"):
|
if low.endswith(".docx"):
|
||||||
tmpdir = tempfile.mkdtemp(prefix="msgnotes-")
|
tmpdir = tempfile.mkdtemp(prefix="msgnotes-")
|
||||||
|
|||||||
+50
-10
@@ -155,6 +155,11 @@ class Template:
|
|||||||
self.sample = {}
|
self.sample = {}
|
||||||
for c, k in zip(self.order, self.kinds):
|
for c, k in zip(self.order, self.kinds):
|
||||||
self.sample.setdefault(k, c)
|
self.sample.setdefault(k, c)
|
||||||
|
# a build is either one slide using ProPresenter's build feature ("Build *DO NOT CLEAR
|
||||||
|
# MID BUILD*"), or one slide per step ("Build 1", "Build 2", ...) - the template decides
|
||||||
|
self.builds = [c for c, k in zip(self.order, self.kinds) if k == "build"]
|
||||||
|
self.step_builds = bool(self.builds) and not any(
|
||||||
|
C.get(el, "build_in") is not None for el in slide_elements(slide_action(self.builds[0])))
|
||||||
if "title" not in self.sample:
|
if "title" not in self.sample:
|
||||||
raise TemplateError("The template has no Title slide (a text box named 'Title').")
|
raise TemplateError("The template has no Title slide (a text box named 'Title').")
|
||||||
if "scripture" not in self.sample or "point" not in self.sample:
|
if "scripture" not in self.sample or "point" not in self.sample:
|
||||||
@@ -747,6 +752,22 @@ def rebuild_builds(a, el, nlines):
|
|||||||
set_repeated(bs, "element_build_order", order)
|
set_repeated(bs, "element_build_order", order)
|
||||||
|
|
||||||
|
|
||||||
|
def build_steps(s):
|
||||||
|
"""Lines shown on each slide when a build is one slide per step: the heading stays up
|
||||||
|
with the first item, then one more line per slide."""
|
||||||
|
n = len(s.lines)
|
||||||
|
first = 2 if (n > 1 and s.lines[0].lead) else 1
|
||||||
|
return list(range(min(first, n), n + 1))
|
||||||
|
|
||||||
|
|
||||||
|
def clip_runs(runs, data):
|
||||||
|
"""The measured font runs of a full build, cut to the text one of its steps shows."""
|
||||||
|
if not runs:
|
||||||
|
return runs
|
||||||
|
n16 = len(rtf_plain(data).encode("utf-16-le")) // 2
|
||||||
|
return [dict(r, len=min(r["len"], n16 - r["loc"])) for r in runs if r["loc"] < n16] or None
|
||||||
|
|
||||||
|
|
||||||
def build(tpl, plan, name, speaker, layout=None, speaker_runs=None):
|
def build(tpl, plan, name, speaker, layout=None, speaker_runs=None):
|
||||||
"""-> .pro bytes. layout: (results, where) from fit(), for exact font runs."""
|
"""-> .pro bytes. layout: (results, where) from fit(), for exact font runs."""
|
||||||
runs = {}
|
runs = {}
|
||||||
@@ -768,7 +789,8 @@ def build(tpl, plan, name, speaker, layout=None, speaker_runs=None):
|
|||||||
for el in slide_elements(a):
|
for el in slide_elements(a):
|
||||||
nm = element_name(el).lower()
|
nm = element_name(el).lower()
|
||||||
if nm in rtfs:
|
if nm in rtfs:
|
||||||
set_text(el, rtfs[nm], runs.get((si, nm)), s.scale.get(nm, 1.0), tpl.style(kind, nm))
|
set_text(el, rtfs[nm], clip_runs(runs.get((si, nm)), rtfs[nm]), s.scale.get(nm, 1.0),
|
||||||
|
tpl.style(kind, nm))
|
||||||
if nm == "build":
|
if nm == "build":
|
||||||
rebuild_builds(a, el, len(texts.get("build", [])))
|
rebuild_builds(a, el, len(texts.get("build", [])))
|
||||||
set_notes(a, tpl.notes_styles.get(kind) or tpl.default_notes, slide_notes(tpl, s))
|
set_notes(a, tpl.notes_styles.get(kind) or tpl.default_notes, slide_notes(tpl, s))
|
||||||
@@ -795,6 +817,15 @@ def build(tpl, plan, name, speaker, layout=None, speaker_runs=None):
|
|||||||
continue # e.g. a picture slide with a template that has none
|
continue # e.g. a picture slide with a template that has none
|
||||||
if kind == "build" and "prebuild" in tpl.sample:
|
if kind == "build" and "prebuild" in tpl.sample:
|
||||||
out.append(clone_cue(tpl.sample["prebuild"]))
|
out.append(clone_cue(tpl.sample["prebuild"]))
|
||||||
|
if kind == "build" and tpl.step_builds:
|
||||||
|
# step k from the template's k-th build slide (the last one again for longer builds)
|
||||||
|
for k, shown in enumerate(build_steps(s)):
|
||||||
|
step = notesmod.Slide("build", s.lines[:shown], src=s.src)
|
||||||
|
step.scale = s.scale
|
||||||
|
cue = clone_cue(tpl.builds[min(k, len(tpl.builds) - 1)])
|
||||||
|
fill(cue, step, si)
|
||||||
|
out.append(cue)
|
||||||
|
continue
|
||||||
cue = clone_cue(tpl.sample[kind])
|
cue = clone_cue(tpl.sample[kind])
|
||||||
if kind in ("picture", "clearpicture"):
|
if kind in ("picture", "clearpicture"):
|
||||||
if s.text.strip():
|
if s.text.strip():
|
||||||
@@ -818,8 +849,19 @@ def build(tpl, plan, name, speaker, layout=None, speaker_runs=None):
|
|||||||
return C.encode(pres)
|
return C.encode(pres)
|
||||||
|
|
||||||
|
|
||||||
|
def cues_for(tpl, s):
|
||||||
|
"""How many slides one plan slide becomes in the built deck."""
|
||||||
|
kind = kind_for(tpl, s)
|
||||||
|
if s.kind == "title" or kind not in tpl.sample:
|
||||||
|
return 0
|
||||||
|
if kind != "build":
|
||||||
|
return 1
|
||||||
|
return (len(build_steps(s)) if tpl.step_builds else 1) + (1 if "prebuild" in tpl.sample else 0)
|
||||||
|
|
||||||
|
|
||||||
def slide_numbers(tpl, plan):
|
def slide_numbers(tpl, plan):
|
||||||
"""The ProPresenter slide number (1-based) each plan slide will have in the built deck."""
|
"""The ProPresenter slide number (1-based) each plan slide will have in the built deck
|
||||||
|
(a build: its first step)."""
|
||||||
nums = []
|
nums = []
|
||||||
n = len(tpl.head)
|
n = len(tpl.head)
|
||||||
title_at = tpl.head.index(tpl.kinds.index("title")) + 1
|
title_at = tpl.head.index(tpl.kinds.index("title")) + 1
|
||||||
@@ -827,20 +869,18 @@ def slide_numbers(tpl, plan):
|
|||||||
if s.kind == "title":
|
if s.kind == "title":
|
||||||
nums.append(title_at)
|
nums.append(title_at)
|
||||||
continue
|
continue
|
||||||
kind = kind_for(tpl, s)
|
c = cues_for(tpl, s)
|
||||||
if kind not in tpl.sample:
|
if not c:
|
||||||
nums.append(None)
|
nums.append(None)
|
||||||
continue
|
continue
|
||||||
if kind == "build" and "prebuild" in tpl.sample:
|
pre = 1 if (kind_for(tpl, s) == "build" and "prebuild" in tpl.sample) else 0
|
||||||
n += 1
|
nums.append(n + pre + 1)
|
||||||
n += 1
|
n += c
|
||||||
nums.append(n)
|
|
||||||
return nums
|
return nums
|
||||||
|
|
||||||
|
|
||||||
def slide_count(tpl, plan):
|
def slide_count(tpl, plan):
|
||||||
nums = [x for x in slide_numbers(tpl, plan) if x]
|
return len(tpl.head) + sum(cues_for(tpl, s) for s in plan.slides) + len(tpl.tail)
|
||||||
return max(nums + [len(tpl.head)]) + len(tpl.tail)
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_pictures(tpl, plan):
|
def ensure_pictures(tpl, plan):
|
||||||
|
|||||||
+20
-1
@@ -561,9 +561,11 @@ def statements_to_slides(group, force_build=False):
|
|||||||
kind = "build" if parallel else "point"
|
kind = "build" if parallel else "point"
|
||||||
else:
|
else:
|
||||||
kind = "build"
|
kind = "build"
|
||||||
|
heading = kind == "build" and build_heading(lines, lead)
|
||||||
out_lines = []
|
out_lines = []
|
||||||
for k, (text, p, mark, part, start) in enumerate(lines):
|
for k, (text, p, mark, part, start) in enumerate(lines):
|
||||||
out_lines.append(Line(underlined(text, p, part, start), lead=(k == 0 and lead and kind == "point")))
|
out_lines.append(Line(underlined(text, p, part, start),
|
||||||
|
lead=(k == 0 and ((lead and kind == "point") or heading))))
|
||||||
s = Slide(kind, out_lines, src=src)
|
s = Slide(kind, out_lines, src=src)
|
||||||
plain = [p for p in group if not p.bold and not p.italic]
|
plain = [p for p in group if not p.bold and not p.italic]
|
||||||
if plain and all(p.size <= 15 for p in plain):
|
if plain and all(p.size <= 15 for p in plain):
|
||||||
@@ -573,6 +575,23 @@ def statements_to_slides(group, force_build=False):
|
|||||||
return [s]
|
return [s]
|
||||||
|
|
||||||
|
|
||||||
|
def build_heading(lines, lead):
|
||||||
|
"""Is a build's first line its heading ("What is your FLAW?" over "1. ... 2. ... 3.")?
|
||||||
|
A heading stays up with the first item when a template shows builds as one slide per step."""
|
||||||
|
if len(lines) < 2:
|
||||||
|
return False
|
||||||
|
if lead:
|
||||||
|
return True
|
||||||
|
first, rest = lines[0], lines[1:]
|
||||||
|
if len(rest) < 2:
|
||||||
|
return False
|
||||||
|
numbered = lambda l: bool(l[2] or NUMBERED_RE.match(l[0]))
|
||||||
|
if all(numbered(l) for l in rest) and not numbered(first):
|
||||||
|
return True
|
||||||
|
word = lambda l: l[0].split(" ")[0].lower()
|
||||||
|
return len({word(l) for l in rest}) == 1 and word(first) != word(rest[0])
|
||||||
|
|
||||||
|
|
||||||
def is_lead(text, p):
|
def is_lead(text, p):
|
||||||
t = text.strip()
|
t = text.strip()
|
||||||
return t.endswith(":") or t.endswith("?") or (p.italic and not p.bold) or (p.italic and p.bold)
|
return t.endswith(":") or t.endswith("?") or (p.italic and not p.bold) or (p.italic and p.bold)
|
||||||
|
|||||||
+4
-3
@@ -10,7 +10,8 @@ HEADER = """# Slide list for: {name}
|
|||||||
# [Title] [Point] [Build] [Subpoint] [Quote] [Sentence] [Scripture] <reference>
|
# [Title] [Point] [Build] [Subpoint] [Quote] [Sentence] [Scripture] <reference>
|
||||||
# [Picture] <what picture> [Clear Picture]
|
# [Picture] <what picture> [Clear Picture]
|
||||||
# The lines under it are the slide's lines (one line = one paragraph; in a Build, one step).
|
# The lines under it are the slide's lines (one line = one paragraph; in a Build, one step).
|
||||||
# In a Point, start a line with > to make it the italic lead-in line.
|
# In a Point, start a line with > to make it the italic lead-in line. In a Build, > marks the
|
||||||
|
# heading (it stays up with the first step).
|
||||||
# In a Scripture, put **double asterisks** around bold words and *single ones* around italic words.
|
# In a Scripture, put **double asterisks** around bold words and *single ones* around italic words.
|
||||||
# On any slide, __double underscores__ around words underline them.
|
# On any slide, __double underscores__ around words underline them.
|
||||||
# Delete a slide by deleting its whole block. Blank lines and lines starting with "# " are ignored.
|
# Delete a slide by deleting its whole block. Blank lines and lines starting with "# " are ignored.
|
||||||
@@ -50,7 +51,7 @@ def dump(plan, name=""):
|
|||||||
out.append("[%s]" % NAMES.get(s.kind, "Point"))
|
out.append("[%s]" % NAMES.get(s.kind, "Point"))
|
||||||
for ln in s.lines:
|
for ln in s.lines:
|
||||||
text = "".join(mark(t, False, False, u) for t, _, _, u in ln.segs)
|
text = "".join(mark(t, False, False, u) for t, _, _, u in ln.segs)
|
||||||
out.append(("> " if (ln.lead and s.kind == "point") else "") + text)
|
out.append(("> " if (ln.lead and s.kind in ("point", "build")) else "") + text)
|
||||||
for item in left.get(len(plan.slides), []):
|
for item in left.get(len(plan.slides), []):
|
||||||
out.append("#! %s" % item[1].replace("\n", " "))
|
out.append("#! %s" % item[1].replace("\n", " "))
|
||||||
return "\n".join(out) + "\n"
|
return "\n".join(out) + "\n"
|
||||||
@@ -125,7 +126,7 @@ def load(text):
|
|||||||
t = line.strip()
|
t = line.strip()
|
||||||
if cur.kind == "scripture":
|
if cur.kind == "scripture":
|
||||||
cur.lines.append(notesmod.Line(parse_bold(t)))
|
cur.lines.append(notesmod.Line(parse_bold(t)))
|
||||||
elif cur.kind == "point" and t.startswith(">"):
|
elif cur.kind in ("point", "build") and t.startswith(">"):
|
||||||
cur.lines.append(notesmod.Line(parse_bold(t[1:].strip(), styles=False), lead=True))
|
cur.lines.append(notesmod.Line(parse_bold(t[1:].strip(), styles=False), lead=True))
|
||||||
else:
|
else:
|
||||||
cur.lines.append(notesmod.Line(parse_bold(t, styles=False)))
|
cur.lines.append(notesmod.Line(parse_bold(t, styles=False)))
|
||||||
|
|||||||
BIN
Binary file not shown.
+37
-1
@@ -144,7 +144,43 @@ def main():
|
|||||||
check("fill-in blanks are not underline marks", plantext.parse_bold("Faith is ______.") ==
|
check("fill-in blanks are not underline marks", plantext.parse_bold("Faith is ______.") ==
|
||||||
[("Faith is ______.", False, False, False)])
|
[("Faith is ______.", False, False, False)])
|
||||||
|
|
||||||
# 8. other campuses: their template names, and who preaches there according to the email
|
# 8. a template with one slide per build step (Build 1, Build 2, ...) instead of the build feature
|
||||||
|
STEP = os.path.join(PRO, "_TEMPLATE_GENERIC - DO NOT DELETE 2609-1.pro")
|
||||||
|
stpl = deck.Template(open(STEP, "rb").read())
|
||||||
|
check("step template detected", stpl.step_builds and len(stpl.builds) == 4 and not tpl.step_builds)
|
||||||
|
p8 = notes.parse(open(os.path.join(NOTES, "Slides - Where is Your Faith - Part 8.rtf"), "rb").read())
|
||||||
|
out = slides_of(deck.build(stpl, p8, "TEST", stpl.speaker()))
|
||||||
|
check("step deck slide count", len(out) == deck.slide_count(stpl, p8), "%d vs %d" % (len(out), deck.slide_count(stpl, p8)))
|
||||||
|
steps = [(lab, texts[0][1]) for lab, texts in out if lab.startswith("Build")]
|
||||||
|
flaw = ["What is your FLAW?", "1. FEARFUL INSECURITY - Gideon and Barak", "2. FLESHLY IMPULSES - David and Samson",
|
||||||
|
"3. FAMILY DYSFUNCTION - David and Jephthah"]
|
||||||
|
want = [("Build %d" % k, " / ".join(flaw[:k + 1])) for k in (1, 2, 3)]
|
||||||
|
check("build steps: heading + one more line per slide", steps[:3] == want, "%s" % steps[:3])
|
||||||
|
check("second build: 3 steps from Build 1-3", [s[0] for s in steps[3:]] == ["Build 1", "Build 2", "Build 3"]
|
||||||
|
and steps[5][1].count(" / ") == 3, "%s" % steps[3:])
|
||||||
|
check("no STREAM DECK slide with step builds", not any("STREAM DECK" in lab for lab, _ in out))
|
||||||
|
nums = deck.slide_numbers(stpl, p8)
|
||||||
|
firsts = [nums[i] for i, s in enumerate(p8.slides) if s.kind == "build"]
|
||||||
|
check("slide numbers point at each build's first step", [out[n - 1][0] for n in firsts] == ["Build 1", "Build 1"],
|
||||||
|
"%s" % firsts)
|
||||||
|
five = notes.Plan()
|
||||||
|
five.slides = [p8.slides[0], notes.Slide("build", [notes.Line([("Heading:", False, False)], lead=True)] +
|
||||||
|
[notes.Line([("Item %d" % k, False, False)]) for k in range(1, 6)])]
|
||||||
|
out5 = [(lab, texts[0][1]) for lab, texts in slides_of(deck.build(stpl, five, "TEST", stpl.speaker()))
|
||||||
|
if lab.startswith("Build")]
|
||||||
|
check("a 5-item build: 5 slides, the last template slide reused",
|
||||||
|
[l for l, _ in out5] == ["Build 1", "Build 2", "Build 3", "Build 4", "Build 4"]
|
||||||
|
and out5[-1][1] == "Heading: / Item 1 / Item 2 / Item 3 / Item 4 / Item 5", "%s" % out5)
|
||||||
|
runs = [{"loc": 0, "len": 10}, {"loc": 10, "len": 30}, {"loc": 40, "len": 5}]
|
||||||
|
data = deck.make_rtf(stpl.style("build", "build"), [(0, [("What is your FLAW?", False, False, False)]),
|
||||||
|
(0, [("1. FEARFUL", False, False, False)])])
|
||||||
|
check("font runs cut to a step's text", deck.clip_runs(runs, data) == [{"loc": 0, "len": 10}, {"loc": 10, "len": 19}])
|
||||||
|
rt = plantext.load(plantext.dump(p8, "x"))
|
||||||
|
check("build heading survives the TextEdit round trip",
|
||||||
|
[l.lead for s in p8.slides if s.kind == "build" for l in s.lines] ==
|
||||||
|
[l.lead for s in rt.slides if s.kind == "build" for l in s.lines])
|
||||||
|
|
||||||
|
# 9. other campuses: their template names, and who preaches there according to the email
|
||||||
import app
|
import app
|
||||||
import settings
|
import settings
|
||||||
for n, want in (("_TEMPLATE - DO NOT DELETE 2608 Lower Thirds.pro", True),
|
for n, want in (("_TEMPLATE - DO NOT DELETE 2608 Lower Thirds.pro", True),
|
||||||
|
|||||||
Reference in New Issue
Block a user