Repository navigation
The files a deck names come from somewhere the caller chooses - #38
Merged
Merged
Conversation
"Chrome" was doing two jobs: it named the shapes a template draws, and it
named the file the definitions of those templates live in. A deck wrote
chrome: ./templates/chrome.md
template: wordmark
and the two keys read as two names for one thing, when one is a path and the
other is a name.
"Chrome" keeps the first job. The file is `templates-file:` now,
`Template\ChromeFile` is `Template\TemplatesFile`, `ChromeNotFound` and
`ChromeTooLarge` are `TemplatesFileNotFound` and `TemplatesFileTooLarge`,
and a templates file that holds a slide is a `MalformedTemplatesFile` rather
than a case of `MalformedChrome`.
The feature is unreleased, so the rename costs nobody a migration.
The library reads every file it needs itself, and the only way in is a path — so a source that keeps its themes in a database cannot serve a deck at all. This adds the interface the library will ask by name, and the default that answers out of directories on this machine. The finder answers and never refuses: a file that is not there is null. The scope check stays with the library, where the messages are, because each half of this suite has its own exceptions — a finder that threw would throw one half's exception and the other half would not catch it, and the two interfaces would stop being the same. That is what lets one adapter serve both. The bases are given per role and by the caller, so the finder is built for the deck at hand rather than for a fixed set of directories. A theme is resolved by its directory first, so a theme directory that holds no manifest still shadows a later one: the behaviour of a theme resolved from a directory, kept.
The theme loader and the resolver read every file of a theme themselves, and the only way in was a path: a source that keeps its themes in a database cannot serve a deck at all. They read through the finder now. The library composes the names — `house/theme.yml`, `house/styles.yml`, `house/layouts.yml`, `house/text-styles.yml` and `house/template.pptx` — and asks, and the finder answers. The scope check stays with the library, where the messages are: each of a theme's own files is resolved and held to the scope before it is read, so a `styles.yml` that is a symlink out of the scope is still refused in this library's own words. The finder answers and never refuses; the library is what knows what a render may read, and a theme that resolves out of the roots is refused by the resolver in the words it already had. `ThemeLoader::load()` and `loadManifest()` take a name and a finder where they took a directory and a scope, and the loader holds the theme's own files to the scope it was given. `ThemeResolver` takes the source of themes as a trailing argument, asks `FileFinder::themes()` for the names that resolve, and keeps the six steps: the directories of themes are the bases the finder is given, so a theme directory without a manifest still shadows a later one. The master is asked for by name like the rest of the theme's files and is still only a path for the renderer to open. The refusal for a master the source cannot serve, the picture reader and the templates file are the next stage.
The theme is read through the finder now, and the deck's own files were not: a picture a deck names was looked for beside the deck and a templates file was read from there, so a source that keeps its files in a database could serve a theme and nothing else. They come from the finder now. A picture is asked for by name — `logo.png` — and a templates file with it, and the scope check stays where it was, in the same terms and with the same messages: a path the finder answers is held to the directories this render may read, and a source that holds the bytes is taken on the caller's word, because there is no path to hold. The ceiling on a templates file stays where it was, with the read. The master is the third file, and it is the one that cannot be read as bytes. A `template.pptx` is a package, and a package is opened as a file, so a source that holds only the bytes has them staged into one — and the staged file is the render's: it is given back when the render is over, whether the deck was drawn or refused. The renderer keeps the master rather than dropping it, so that the render is what gives the file back and not a destructor that may run much later. A master the theme carries and the source cannot serve is refused by name. A null answer on its own says nothing: the theme may carry no master, or the source may not be able to serve one. The listing of a theme's files tells the two apart, and the second is a failure — a deck drawn without the master's geometry is in the wrong design, with nothing in the file saying so. `Exception\UnservedTemplate` says so, and the deck is not drawn at all.
Two things a program drawing decks could not do: name the theme itself, and take the
deck away as bytes rather than as a path.
`PresentationRenderer::withTheme(string $name): self` names the theme, and it outranks
the deck's own `theme:` — the same rule the Word half follows, where a theme named in
code answers for the document. The name is resolved through the finder when it is given
and the theme that came back is kept, so the name is asked for once and not once for the
name and once for the deck. The deck's frontmatter is still checked: this half refuses
an unknown key or a wrong-typed value by name, and a deck writing `theme: 42` is still
refused whatever theme it is drawn in.
`PresentationRenderer::toString(Deck $deck): string` is the counterpart of the Word
half's `toDocx()`: the deck, as the bytes of a `.pptx`. A `.pptx` is a package, and
PHP's `ZipArchive` cannot write one to memory — `open('php://temp')` answers 9 and
`open('php://temp', CREATE | OVERWRITE)` fails at `close()` — and PHPPresentation's
writer takes a path. So the deck is written to a file of the library's own, read back
and given back, at the mode of a file nobody else on the machine can read. Nothing of
the library's is left on the disk: neither the file the deck was written to, nor the
master or the pictures the render staged, whether the deck was written or the write
failed.
A theme kept across renders keeps its master too, and the master is written again when
it is asked for: the file the library staged for it is given back at the end of every
render, and the render after that needs it there.
The rule the two halves' finders follow is this half's: a name that is already a path
is taken as it stands, whatever the role it is asked for in. The Word half follows it
now, and the tests here pin it for every role — the `theme` role above all, where a
name is `<theme>/<file>` and an absolute one used to be split on its first slash.
…ey do `php:S1142` fires on `DiskFinder::resolve()` and on `TemplatesFile::fromSource()`: both had four returns, and the rule allows three. Neither was one method doing one thing, so neither is closed by folding the returns into one. `DiskFinder::resolve()` asks three questions: is the name already a path, is it a theme's file, and which base holds it. The third now goes to a helper — `inWorlds()` — which is the shape the Word half's copy of this finder already has, so the two halves answer the same questions of the same names. The list of bases went with it: `bases()` is `worlds()` now, so the helper and the list it walks are named for the same thing. `TemplatesFile::fromSource()` asked a source for a path and, failing that, for the contents: two rules, two failures and two ceilings in one method. `fromPath()` holds the path to the scope and reads the file; `fromContents()` takes the contents on the caller's word and measures them against the ceiling. The path is asked for first, because a source that answers one holds the file as a file, and only a source that answers none holds the contents. Nothing about the answers changes. The same names resolve to the same paths, the same refusals carry the same words, and the two halves of the suite were set against each other over every role, every kind of name and every base — and every answer was the same.
…ir own `php:S1448` says the renderer has too many methods, and the finding says what to do about it: split it. Twenty-three was over the ceiling of twenty. The theme a deck is drawn in is a thing the renderer works with rather than a step of the walk that draws it, and so are the faces the deck, its theme and its chrome name. `Render\DeckTheme` resolves the theme, reads the role of each slide and takes the boxes a theme's own `template.pptx` gives them; `Render\DeckFaces` finds the faces there are no advance widths for and records where each of them was named. Nothing a caller can see has changed: the same methods, the same messages and the same order. A deck over the ceiling is still refused before its theme is resolved, and nothing the library staged is left on the disk whether the deck was written or refused. A test pins the order, because a split that moved the theme ahead of the count would answer with the theme rather than the ceiling, and nothing else would say so.
…er alike A finder that names a theme's master and cannot serve it was drawn without: the master came back as a path, the path did not resolve, and the deck was drawn in this library's geometry with nothing in the file saying so. That is the failure `UnservedTemplate` exists for, and the README claims the refusal without it. A source that answers a path for a master it cannot serve is now refused the same way as one whose listing names a master. The loader resolves the path of a theme's own files whether or not it has a scope, so this half and the Word half answer the same way: the path that reaches `LoadedTheme::directory()` and the failures that name one is the resolved path in both. The four public parameters that take a `PathScope` are named `$scope` rather than `$files`, which is what they are. The comments around them were wrong or missing. The writer replaces the file it writes into and keeps its mode, so the account of the window in which a staged deck's permissions could be narrowed was not the one that happens; a staged picture is narrowed like the other two staged files; a finder answers with the path as it was written; the theme a deck is drawn in comes from the resolver's finder and not the renderer's; and a `DiskFinder` is built for the themes of a render and for nothing else.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



A theme that did not come from a directory, and a deck that comes back as bytes.
The finder
This half read every file itself, so the only way in was a path. A program that keeps its themes in a database had nowhere to put them. This adds one seam —
Files\FileFinder— and every file a deck names comes through it.The finder owns where files come from; the library owns what a theme is. The library composes the names and asks; the finder answers. It never needs to know what a theme is, or what the files of one are called.
read()— the contents. What the library wants for YAML, for Markdown and for a picture.path()— a path, for the two things a zip-based reader must open: a theme'stemplate.pptxand a document'stemplate_file:.themes()— the themes that resolve, so a failure can name what would have worked.files()— the files of a theme, so a null can be told apart from a refusal.A finder answers; it does not refuse. A file that is not there is
nulland nothing else. Each half of this suite has its own exceptions, so a finder that refused would throw one half's exception and the other half would not catch it — and the two interfaces would stop being the same, which is the whole reason there is one shape rather than two. What may be read therefore stays with the library, which holds every path a finder answers to its own roots before it reads it.The interface is identical to the Word half's, and the two
DiskFinders are behaviourally identical — the same names, the same roles, the same answers, checked by loading both classes into one process and asking both the same questions. A finder written once serves both.What a caller can do now
withTheme()resolves a name once. The deck's owntheme:is still type-checked — this half refuses a wrong-typed frontmatter value by name, and a caller's theme does not stop that — but the caller's theme answers for the deck.toString()is the counterpart oftoDocx(). The renderer wrote to a path and returned the path; every embedder wrote a temporary file and read it back. It stages now, the way the Word half already did: PHP'sZipArchivecannot write to memory (open('php://temp')returns 9, andopen('php://temp', CREATE|OVERWRITE)fails atclose()with "Failure to create temporary file"), and PHPPresentation's writer takes a path.What did not change
The error messages, the shipped themes, the six steps a theme is looked for in, the scope checks, and this half's refusing an unknown directive or frontmatter key by name. The scope check stays in the library, where the messages are — the finder resolves a path, the library holds it to its roots and reads it.
A theme directory that exists but holds no manifest still shadows a later one, and is still reported as a theme without a manifest.
The new refusal
A theme may carry a
template.pptx. A source that lists the master and cannot serve it is refused by name, with the newUnservedTemplate:It is not an
InvalidInput: neither the deck's author nor the theme's author can fix it — the program that brought the source can.The listing (
files()) is what tells "this theme has no master" from "this source cannot serve one".Nothing left on disk
A master or a picture that a source serves as bytes is staged into a file of the library's own, and removed when the render is over — on the failure path as well as the happy one.
render()andtoString()both release what they staged, in afinally.The deliberate breaks
ThemeLoader::load()andloadManifest()take a?FileFinderwhere they took a?PathScope; their first parameter is now a theme's name rather than its directory — the signature does not change, the interpretation does.ThemeResolver's loader parameter became nullable.PresentationRenderer::__construct()gained a trailing namedfiles:.LoadedTheme::template()returns aMasterrather than a path, because a path alone cannot say who owns the file — and a staged master is written again when it is asked for, so a renderer can draw twice.