You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
187 lines
6.9 KiB
187 lines
6.9 KiB
<#
|
|
.SYNOPSIS
|
|
Appends a dated entry to the Daniel/Ahmed 1:1 notebook tracker (.docx) using Word COM.
|
|
|
|
.DESCRIPTION
|
|
Clones the formatting of the existing "Example:" block (numbered question headings at
|
|
list level 1, lettered content bullets at list level 2) so new entries match the
|
|
tracker's look exactly, without touching the document's MIP sensitivity label or any
|
|
existing content.
|
|
|
|
Every entry starts on a new page: a manual page break (Ctrl+Enter) is emitted ahead of
|
|
the date heading.
|
|
|
|
Entry content is supplied as a JSON file so the caller never has to fight PowerShell
|
|
quoting. Shape:
|
|
|
|
{
|
|
"achievements": ["bullet one", "bullet two"],
|
|
"next": ["bullet"],
|
|
"lessons": ["bullet"],
|
|
"topics": ["bullet"]
|
|
}
|
|
|
|
Any section may be omitted or empty; it renders a single "None." bullet.
|
|
|
|
.PARAMETER DocPath
|
|
Full path to the tracker .docx.
|
|
|
|
.PARAMETER JsonPath
|
|
Full path to the UTF-8 JSON file described above.
|
|
|
|
.PARAMETER EntryDate
|
|
Date heading for the entry, MM/DD/yyyy. Defaults to today.
|
|
|
|
.PARAMETER Position
|
|
Top - insert as the newest entry, immediately after the Example block (default).
|
|
Bottom - append at the end of the document (chronological).
|
|
|
|
.PARAMETER Preview
|
|
Render the entry to the console and exit without modifying the document.
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$DocPath,
|
|
[Parameter(Mandatory = $true)][string]$JsonPath,
|
|
[string]$EntryDate = (Get-Date -Format 'MM/dd/yyyy'),
|
|
[ValidateSet('Top', 'Bottom')][string]$Position = 'Top',
|
|
[switch]$Preview
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$SectionHeadings = @(
|
|
'What are my Achievements, Impact and Key Results?',
|
|
"What's next? / What am I working on?",
|
|
'Lessons Learned/Things to Improve',
|
|
'Topics to discuss/What I need help with'
|
|
)
|
|
|
|
if (-not (Test-Path -LiteralPath $DocPath)) { throw "Tracker not found: $DocPath" }
|
|
if (-not (Test-Path -LiteralPath $JsonPath)) { throw "Entry JSON not found: $JsonPath" }
|
|
|
|
$entry = Get-Content -LiteralPath $JsonPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
|
|
function Get-Bullets($value) {
|
|
if ($null -eq $value) { return , @('None.') }
|
|
$list = @($value | Where-Object { $_ -and $_.ToString().Trim() } | ForEach-Object { $_.ToString().Trim() })
|
|
if ($list.Count -eq 0) { return , @('None.') }
|
|
return , $list
|
|
}
|
|
|
|
$sections = @(
|
|
(Get-Bullets $entry.achievements),
|
|
(Get-Bullets $entry.next),
|
|
(Get-Bullets $entry.lessons),
|
|
(Get-Bullets $entry.topics)
|
|
)
|
|
|
|
if ($Preview) {
|
|
Write-Output $EntryDate
|
|
for ($i = 0; $i -lt 4; $i++) {
|
|
Write-Output (" {0}. {1}" -f ($i + 1), $SectionHeadings[$i])
|
|
foreach ($b in $sections[$i]) { Write-Output " - $b" }
|
|
}
|
|
return
|
|
}
|
|
|
|
# --- Word COM -------------------------------------------------------------
|
|
|
|
$wdCharacter = 1
|
|
$word = $null
|
|
$doc = $null
|
|
$openedByUs = $false
|
|
|
|
function Get-ParaText($para) { return ($para.Range.Text -replace "[\r\a]", '') }
|
|
|
|
function Set-ParaText($para, [string]$text) {
|
|
$r = $para.Range
|
|
if ($r.End - $r.Start -gt 0) { [void]$r.MoveEnd($wdCharacter, -1) }
|
|
$r.Text = $text
|
|
}
|
|
|
|
try {
|
|
try { $word = [Runtime.InteropServices.Marshal]::GetActiveObject('Word.Application') }
|
|
catch { $word = New-Object -ComObject Word.Application; $word.Visible = $false }
|
|
|
|
$full = (Resolve-Path -LiteralPath $DocPath).Path
|
|
foreach ($d in $word.Documents) { if ($d.FullName -eq $full) { $doc = $d; break } }
|
|
if (-not $doc) { $doc = $word.Documents.Open($full); $openedByUs = $true }
|
|
|
|
# Locate the Example block: the first bare MM/DD/YYYY paragraph through the paragraph
|
|
# before the next bare-date paragraph (or the last non-empty paragraph).
|
|
$paras = $doc.Paragraphs
|
|
$dateIdx = @()
|
|
for ($i = 1; $i -le $paras.Count; $i++) {
|
|
if ((Get-ParaText $paras.Item($i)).Trim() -match '^\d{1,2}/\d{1,2}/\d{4}$') { $dateIdx += $i }
|
|
}
|
|
if ($dateIdx.Count -eq 0) { throw 'Could not find the Example block (no MM/DD/YYYY paragraph) in the tracker.' }
|
|
|
|
$exStart = $dateIdx[0]
|
|
$exEnd = if ($dateIdx.Count -gt 1) { $dateIdx[1] - 1 } else { $paras.Count }
|
|
while ($exEnd -gt $exStart -and (Get-ParaText $paras.Item($exEnd)).Trim() -eq '') { $exEnd-- }
|
|
|
|
# Formatting seeds harvested from the Example block.
|
|
$seedPlain = $null # bare paragraph (the date line)
|
|
$seedHead = $null # list level 1 (the four questions)
|
|
$seedBullet = $null # list level 2 (content under a question)
|
|
for ($i = $exStart; $i -le $exEnd; $i++) {
|
|
$p = $paras.Item($i)
|
|
$lvl = $p.Range.ListFormat.ListLevelNumber
|
|
if ($i -eq $exStart) { $seedPlain = $p.Range }
|
|
elseif ($lvl -eq 1 -and -not $seedHead) { $seedHead = $p.Range }
|
|
elseif ($lvl -eq 2 -and -not $seedBullet) { $seedBullet = $p.Range }
|
|
}
|
|
if (-not $seedPlain -or -not $seedHead -or -not $seedBullet) {
|
|
throw 'Example block is missing a plain / level-1 / level-2 paragraph to use as a formatting seed.'
|
|
}
|
|
|
|
# Snapshot the seeds' formatted text before inserting (live ranges shift as we edit).
|
|
$fmtPlain = $seedPlain.FormattedText
|
|
$fmtHead = $seedHead.FormattedText
|
|
$fmtBullet = $seedBullet.FormattedText
|
|
|
|
# Insertion point: end of the Example block (Top) or end of the document (Bottom).
|
|
if ($Position -eq 'Top') {
|
|
$pos = $paras.Item($exEnd).Range.End
|
|
}
|
|
else {
|
|
$lastIdx = $paras.Count
|
|
while ($lastIdx -gt 1 -and (Get-ParaText $paras.Item($lastIdx)).Trim() -eq '') { $lastIdx-- }
|
|
$pos = $paras.Item($lastIdx).Range.End
|
|
}
|
|
|
|
# Emit one paragraph cloned from a seed, set its text, advance the cursor.
|
|
$emit = {
|
|
param($fmt, [string]$text)
|
|
$r = $doc.Range($pos, $pos)
|
|
$r.FormattedText = $fmt
|
|
$p = $doc.Range($r.Start, $r.End).Paragraphs.Item(1)
|
|
Set-ParaText $p $text
|
|
$script:pos = $p.Range.End
|
|
}
|
|
|
|
# Each entry starts on its own page: chr(12) is Word's manual page break (Ctrl+Enter),
|
|
# embedded at the head of the date line so no extra paragraph is introduced.
|
|
& $emit $fmtPlain ("$([char]12)$EntryDate")
|
|
for ($s = 0; $s -lt 4; $s++) {
|
|
& $emit $fmtHead $SectionHeadings[$s]
|
|
foreach ($b in $sections[$s]) { & $emit $fmtBullet $b }
|
|
}
|
|
|
|
# Strip stray page-break characters Word may leave in trailing empty paragraphs,
|
|
# which would otherwise render a blank page at the end of the document.
|
|
for ($i = $doc.Paragraphs.Count; $i -ge 1; $i--) {
|
|
$t = $doc.Paragraphs.Item($i).Range.Text -replace "[\r\a]", ''
|
|
if ($t.Trim() -ne '') { break }
|
|
if ($t -match [string][char]12) { Set-ParaText $doc.Paragraphs.Item($i) '' }
|
|
}
|
|
|
|
$doc.Save()
|
|
Write-Output "Wrote entry $EntryDate to $full (position: $Position)."
|
|
}
|
|
finally {
|
|
if ($doc -and $openedByUs) { $doc.Close([ref]$true) | Out-Null }
|
|
if ($word -and $openedByUs -and $word.Documents.Count -eq 0) { $word.Quit() | Out-Null }
|
|
[System.GC]::Collect()
|
|
}
|
|
|