| 485 |
485 |
|
emit_cells(columns, cells, opts, out, None);
|
| 486 |
486 |
|
}
|
| 487 |
487 |
|
|
|
488 |
+ |
/// Where one column's markup went, and which of it the cell wrote.
|
|
489 |
+ |
///
|
|
490 |
+ |
/// Two ranges because two different things vary, and a caller compiling this
|
|
491 |
+ |
/// into a template needs to say which it means.
|
|
492 |
+ |
///
|
|
493 |
+ |
/// `block` is the whole `<div>`: the column emits one per column whether or not
|
|
494 |
+ |
/// a cell answers it, so a caller replacing a cell with a different one replaces
|
|
495 |
+ |
/// the block, class and all.
|
|
496 |
+ |
///
|
|
497 |
+ |
/// `content` is what the cell itself contributed. A cell that is absent leaves
|
|
498 |
+ |
/// the block standing and empty, so a caller compiling "this cell is there or
|
|
499 |
+ |
/// is not" must cover the content alone -- covering the block would delete a
|
|
500 |
+ |
/// `<div>` the row still draws, and the row would lose a column.
|
|
501 |
+ |
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
502 |
+ |
pub struct Placed {
|
|
503 |
+ |
/// The whole column block, opening tag to closing tag.
|
|
504 |
+ |
pub block: core::ops::Range<usize>,
|
|
505 |
+ |
/// What the cell wrote inside it, empty range and all.
|
|
506 |
+ |
pub content: core::ops::Range<usize>,
|
|
507 |
+ |
}
|
|
508 |
+ |
|
| 488 |
509 |
|
/// A row's cells, saying where each column's block landed.
|
| 489 |
510 |
|
///
|
| 490 |
511 |
|
/// Byte-identical to [`cells_html_into`], and it appends one entry to `placed`
|
| 507 |
528 |
|
cells: &[Cell<'_>],
|
| 508 |
529 |
|
opts: &Emit,
|
| 509 |
530 |
|
out: &mut String,
|
| 510 |
|
- |
placed: &mut Vec<core::ops::Range<usize>>,
|
|
531 |
+ |
placed: &mut Vec<Placed>,
|
| 511 |
532 |
|
) {
|
| 512 |
533 |
|
emit_cells(columns, cells, opts, out, Some(placed));
|
| 513 |
534 |
|
}
|
| 517 |
538 |
|
cells: &[Cell<'_>],
|
| 518 |
539 |
|
opts: &Emit,
|
| 519 |
540 |
|
out: &mut String,
|
| 520 |
|
- |
mut placed: Option<&mut Vec<core::ops::Range<usize>>>,
|
|
541 |
+ |
mut placed: Option<&mut Vec<Placed>>,
|
| 521 |
542 |
|
) {
|
| 522 |
543 |
|
for column in columns {
|
| 523 |
544 |
|
let at = out.len();
|
| 531 |
552 |
|
push_class(out, cell_part_class(part), opts);
|
| 532 |
553 |
|
}
|
| 533 |
554 |
|
out.push_str("\">");
|
|
555 |
+ |
let content = out.len();
|
| 534 |
556 |
|
out.push_str(found.map_or("", |cell| cell.content.0));
|
|
557 |
+ |
let wrote = content..out.len();
|
| 535 |
558 |
|
out.push_str("</div>");
|
| 536 |
559 |
|
if let Some(placed) = placed.as_deref_mut() {
|
| 537 |
|
- |
placed.push(at..out.len());
|
|
560 |
+ |
placed.push(Placed {
|
|
561 |
+ |
block: at..out.len(),
|
|
562 |
+ |
content: wrote,
|
|
563 |
+ |
});
|
| 538 |
564 |
|
}
|
| 539 |
565 |
|
}
|
| 540 |
566 |
|
}
|
| 583 |
609 |
|
|
| 584 |
610 |
|
assert_eq!(said.strip_prefix("before:").unwrap(), plain);
|
| 585 |
611 |
|
assert_eq!(placed.len(), columns.len());
|
| 586 |
|
- |
assert_eq!(placed[0].start, "before:".len());
|
| 587 |
|
- |
assert_eq!(placed[1].end, said.len());
|
| 588 |
|
- |
for range in &placed {
|
| 589 |
|
- |
let block = &said[range.clone()];
|
|
612 |
+ |
assert_eq!(placed[0].block.start, "before:".len());
|
|
613 |
+ |
assert_eq!(placed[1].block.end, said.len());
|
|
614 |
+ |
for one in &placed {
|
|
615 |
+ |
let block = &said[one.block.clone()];
|
| 590 |
616 |
|
assert!(block.starts_with("<div class=\""), "{block}");
|
| 591 |
617 |
|
assert!(block.ends_with("</div>"), "{block}");
|
| 592 |
618 |
|
}
|
| 593 |
|
- |
assert!(said[placed[1].clone()].contains("<b>Free</b>"));
|
|
619 |
+ |
|
|
620 |
+ |
// The content is what the cell wrote and nothing the column wrote, so
|
|
621 |
+ |
// it is the half a caller covers when the cell may be absent: the
|
|
622 |
+ |
// block stands either way.
|
|
623 |
+ |
assert_eq!(&said[placed[0].content.clone()], "Kick");
|
|
624 |
+ |
assert_eq!(&said[placed[1].content.clone()], "<b>Free</b>");
|
|
625 |
+ |
for one in &placed {
|
|
626 |
+ |
assert!(one.block.start < one.content.start);
|
|
627 |
+ |
assert!(one.content.end < one.block.end);
|
|
628 |
+ |
}
|
|
629 |
+ |
|
|
630 |
+ |
// A column no cell answers still draws its block, and the content it
|
|
631 |
+ |
// reports is the empty range inside it. That is the case the whole
|
|
632 |
+ |
// split exists for.
|
|
633 |
+ |
let mut none = String::new();
|
|
634 |
+ |
let mut empty = Vec::new();
|
|
635 |
+ |
cells_html_placed(&columns, &[], &opts, &mut none, &mut empty);
|
|
636 |
+ |
assert_eq!(empty.len(), columns.len());
|
|
637 |
+ |
for one in &empty {
|
|
638 |
+ |
assert!(one.content.is_empty(), "{one:?}");
|
|
639 |
+ |
assert!(!none[one.block.clone()].is_empty());
|
|
640 |
+ |
}
|
| 594 |
641 |
|
}
|
| 595 |
642 |
|
|
| 596 |
643 |
|
#[test]
|