diff --git a/Test/public/convertMeetingAttendeesToMarkdown.test.ps1 b/Test/public/convertMeetingAttendeesToMarkdown.test.ps1 new file mode 100644 index 0000000..8b2c0ed --- /dev/null +++ b/Test/public/convertMeetingAttendeesToMarkdown.test.ps1 @@ -0,0 +1,196 @@ +function Test_ConvertMeetingAttendeesToMarkdown_EmptyInput { + + # Arrange + $input = "" + + # Act + $result = Convert-NotesMeetingAttendeesToMarkdown -MeetingAttendees $input + + # Assert + Assert-AreEqual -Expected "" -Presented $result -Comment "Empty input should return empty string" +} + +function Test_ConvertMeetingAttendeesToMarkdown_HeaderOnly { + + # Arrange + $input = "Name`tAttendance`tResponse" + + # Act + $result = Convert-NotesMeetingAttendeesToMarkdown -MeetingAttendees $input + + # Assert + Assert-AreEqual -Expected "" -Presented $result -Comment "Header-only input should return empty string" +} + +function Test_ConvertMeetingAttendeesToMarkdown_SingleAttendee { + + # Arrange + $input = @" +Name`tAttendance`tResponse +André Müller `tRequired`tAccepted +"@ + + # Act + $result = Convert-NotesMeetingAttendeesToMarkdown -MeetingAttendees $input + + # Assert + $expected = @' +- Devtools (1) + - ✅R André Müller +'@ + Assert-AreEqual -Expected $expected -Presented $result -Comment "Single attendee should include attendance and response" +} + +function Test_ConvertMeetingAttendeesToMarkdown_MultipleCompanies { + + # Arrange + $input = @" +Name`tAttendance`tResponse +Alice Johnson `tRequired`tAccepted +Bob Smith `tOptional`tDeclined +Charlie Chen `tOptional`tAccepted +"@ + + # Act + $result = Convert-NotesMeetingAttendeesToMarkdown -MeetingAttendees $input + + # Assert + $expected = @' +- Alphatech (2) + - ✅R Alice Johnson + - ✅O Charlie Chen +- Betasoft (1) + - ❎O Bob Smith +'@ + Assert-AreEqual -Expected $expected -Presented $result -Comment "Multiple companies should be sorted with attendance info" +} + +function Test_ConvertMeetingAttendeesToMarkdown_AllCapsConversion { + + # Arrange + $input = @" +Name`tAttendance`tResponse +ALICE JOHNSON `tRequired`tAccepted +BOB SMITH `tOptional`tAccepted +"@ + + # Act + $result = Convert-NotesMeetingAttendeesToMarkdown -MeetingAttendees $input + + # Assert + $expected = @' +- Alphatech (1) + - ✅R Alice Johnson +- Betasoft (1) + - ✅O Bob Smith +'@ + Assert-AreEqual -Expected $expected -Presented $result -Comment "ALL CAPS names should be converted to title case" +} + +function Test_ConvertMeetingAttendeesToMarkdown_EmailOnlyFormat { + + # Arrange + $input = @" +Name`tAttendance`tResponse +john.davis@acmecorp.com `tRequired`tAccepted +"@ + + # Act + $result = Convert-NotesMeetingAttendeesToMarkdown -MeetingAttendees $input + + # Assert + $expected = @' +- Acmecorp (1) + - ✅R john.davis@acmecorp.com +'@ + Assert-AreEqual -Expected $expected -Presented $result -Comment "Email-only format should show email with attendance info" +} + +function Test_ConvertMeetingAttendeesToMarkdown_SpaceSeparated { + + # Arrange + $input = @" +Name Attendance Response +Alice Johnson Required Accepted +Bob Smith Optional Declined +"@ + + # Act + $result = Convert-NotesMeetingAttendeesToMarkdown -MeetingAttendees $input + + # Assert + $expected = @' +- Alphatech (1) + - ✅R Alice Johnson +- Betasoft (1) + - ❎O Bob Smith +'@ + Assert-AreEqual -Expected $expected -Presented $result -Comment "Space-separated fields should be parsed correctly" +} + +function Test_ConvertMeetingAttendeesToMarkdown_NoSeparator { + + # Arrange + $input = @" +NameAttendanceResponse +Alice Johnson RequiredAccepted +Bob Smith OptionalDeclined +"@ + + # Act + $result = Convert-NotesMeetingAttendeesToMarkdown -MeetingAttendees $input + + # Assert + $expected = @' +- Alphatech (1) + - ✅R Alice Johnson +- Betasoft (1) + - ❎O Bob Smith +'@ + Assert-AreEqual -Expected $expected -Presented $result -Comment "Concatenated fields with no separator should be parsed correctly" +} + +function Test_ConvertMeetingAttendeesToMarkdown_BigSample { + + # Arrange + $input = @" +Name`tAttendance`tResponse +André Müller `tRequired`tAccepted +john.davis@acmecorp.com `tRequired`tAccepted +EMMA WATSON CLARK `tRequired`tAccepted +Grace Kim `tOptional`tAccepted +Helen de la Cruz Torres `tOptional`tAccepted +FRANK MILLER THOMPSON `tOptional`tAccepted +David Parker Lane `tOptional`tFollowing +LAURA CHEN WANG `tOptional`tDeclined +Mark Stevens (External Consulting LLC) `tRequired`tDidn't respond +Nathan Brooks Wilson `tOptional`tDidn't respond +PETER JONES GARCIA `tOptional`tDidn't respond +SARAH TAYLOR RODRIGUEZ `tOptional`tDidn't respond +René Dubois Martín `tOptional`tDidn't respond +"@ + + # Act + $result = Convert-NotesMeetingAttendeesToMarkdown -MeetingAttendees $input + + # Assert + $expected = @' +- Acmecorp (6) + - ✅R Emma Watson Clark + - ✅R john.davis@acmecorp.com + - ✅O Frank Miller Thompson + - ❎O Laura Chen Wang + - 🟩O Peter Jones Garcia + - 🟩O Sarah Taylor Rodriguez +- Cloudtech (5) + - ✅O Grace Kim + - ✅O Helen de la Cruz Torres + - 👀O David Parker Lane + - 🟩O Nathan Brooks Wilson + - 🟩O René Dubois Martín +- Devtools (2) + - ✅R André Müller + - ⭕️R Mark Stevens (External Consulting LLC) +'@ + Assert-AreEqual -Expected $expected -Presented $result -Comment "Big sample should match expected output with attendance and RSVP" +} diff --git a/Test/public/convertMeetingMembersToMarkdown.test.ps1 b/Test/public/convertMeetingMembersToMarkdown.test.ps1 index 164afc5..0937ce8 100644 --- a/Test/public/convertMeetingMembersToMarkdown.test.ps1 +++ b/Test/public/convertMeetingMembersToMarkdown.test.ps1 @@ -156,6 +156,83 @@ function Test_ConvertMeetingMembersToMarkdown_MixedEmailFormats { } +function Test_ConvertMeetingMembersToMarkdown_OutlookFormat { + + # Arrange - Outlook format with ALL CAPS names, email-only entries, and mixed case + $input = "ALICE JOHNSON ; BOB SMITH ; charlie@gammatech.com " + + # Act + $result = Convert-NotesMeetingMembersToMarkdown -MeetingMembers $input + + # Assert + $expected = @" +- Alphatech (1) + - Alice Johnson +- Betasoft (1) + - Bob Smith +- Gammatech (1) + - charlie@gammatech.com +"@ + Assert-AreEqual -Expected $expected -Presented $result -Comment "Outlook format should handle ALL CAPS, email-only, and mixed case entries" +} + +function Test_ConvertMeetingMembersToMarkdown_OutlookResourceFiltering { + + # Arrange + $input = "ALICE JOHNSON ; ES-Ciudad Room 101 " + + # Act + $result = Convert-NotesMeetingMembersToMarkdown -MeetingMembers $input + + # Assert + $expected = @" +- Alphatech (1) + - Alice Johnson +"@ + Assert-AreEqual -Expected $expected -Presented $result -Comment "Resource/room entries should be filtered out" +} + +function Test_ConvertMeetingMembersToMarkdown_OutlookMixedCasePreserved { + + # Arrange - Mixed case names should NOT be converted + $input = "Vilanova Arnal, Juan ; Ricardo Sastre Martín " + + # Act + $result = Convert-NotesMeetingMembersToMarkdown -MeetingMembers $input + + # Assert + $expected = @" +- Accenture (1) + - Vilanova Arnal, Juan +- Microsoft (1) + - Ricardo Sastre Martín +"@ + Assert-AreEqual -Expected $expected -Presented $result -Comment "Mixed case names should be preserved as-is" +} + +function Test_ConvertMeetingMembersToMarkdown_OutlookBigSample { + + # Arrange + $input = "JUAN CARLOS OSORIO BARJOLA ; juan.a.mora@bbva.com ; rulasg@github.com ; Vilanova Arnal, Juan ; ES-Room ; dmangas@microsoft.com " + + # Act + $result = Convert-NotesMeetingMembersToMarkdown -MeetingMembers $input + + # Assert + $expected = @" +- Accenture (1) + - Vilanova Arnal, Juan +- Bbva (2) + - Juan Carlos Osorio Barjola + - juan.a.mora@bbva.com +- Github (1) + - rulasg@github.com +- Microsoft (1) + - dmangas@microsoft.com +"@ + Assert-AreEqual -Expected $expected -Presented $result -Comment "Outlook format with mixed entry types should work correctly" +} + function Test_ConvertMeetingsMembersToMarkdown_Big_sample{ $imput = @" diff --git a/include/MyWrite.ps1 b/include/MyWrite.ps1 index 7e8ffbb..1ecc794 100644 --- a/include/MyWrite.ps1 +++ b/include/MyWrite.ps1 @@ -53,15 +53,25 @@ function Write-MyHost { [Parameter()][string]$ForegroundColor = $OUTPUT_COLOR, [Parameter()][switch]$NoNewLine ) + + Write-MyDebug -Section "MyHost" -Message $Message + # Write-Host $message -ForegroundColor $OUTPUT_COLOR Write-ToConsole $message -Color $ForegroundColor -NoNewLine:$NoNewLine } +function Clear-MyHost { + [CmdletBinding()] + param() + + Clear-Host +} + function Write-MyDebug { [CmdletBinding()] [Alias("Write-Debug")] param( - [Parameter(Position = 0)][string]$section, + [Parameter(Position = 0)][string]$section = "none", [Parameter(Position = 1, ValueFromPipeline)][string]$Message, [Parameter(Position = 2)][object]$Object ) @@ -75,8 +85,40 @@ function Write-MyDebug { $message = $message + " - " + $objString } $timestamp = Get-Date -Format 'HH:mm:ss.fff' - "[$timestamp][D][$section] $message" | Write-ToConsole -Color $DEBUG_COLOR + + # Write on host + $logMessage ="[$timestamp][$MODULE_NAME][D][$section] $message" + + $logMessage | Write-ToConsole -Color $DEBUG_COLOR + $logMessage | Write-MyDebugLogging + } + } +} + +function Write-MyDebugLogging { + param( + [Parameter(Position = 1, ValueFromPipeline)][string]$LogMessage + ) + + process{ + + $loggingFilePath = get-DebugLogFile + + # Check if logging is enabled + if ([string]::IsNullOrWhiteSpace( $loggingFilePath )) { + return + } + + # Check if file exists + # This should always exist as logging checks for parent path to be enabled + # It may happen if since enable to execution the parent folder aka loggingFilePath is deleted. + if(-not (Test-Path -Path $loggingFilePath -PathType Leaf) ){ + Write-Warning "Debug logging file path not accesible : '$loggingFilePath'" + return $false } + + # Write to log file + Add-Content -Path $loggingFilePath -Value $LogMessage } } @@ -101,8 +143,7 @@ function Test-MyVerbose { [Parameter(Position = 0)][string]$section ) - $moduleDebugVarName = $MODULE_NAME + "_VERBOSE" - $flag = [System.Environment]::GetEnvironmentVariable($moduleDebugVarName) + $flag = get-VerboseSections if ([string]::IsNullOrWhiteSpace( $flag )) { return $false @@ -123,8 +164,7 @@ function Enable-ModuleNameVerbose{ $flag = $section } - $moduleDebugVarName = $MODULE_NAME + "_VERBOSE" - [System.Environment]::SetEnvironmentVariable($moduleDebugVarName, $flag) + set-VerboseSections $flag } Copy-Item -path Function:Enable-ModuleNameVerbose -Destination Function:"Enable-$($MODULE_NAME)Verbose" Export-ModuleMember -Function "Enable-$($MODULE_NAME)Verbose" @@ -132,59 +172,132 @@ Export-ModuleMember -Function "Enable-$($MODULE_NAME)Verbose" function Disable-ModuleNameVerbose{ param() - $moduleDebugVarName = $MODULE_NAME + "_VERBOSE" - [System.Environment]::SetEnvironmentVariable($moduleDebugVarName, $null) + set-VerboseSections $null } Copy-Item -path Function:Disable-ModuleNameVerbose -Destination Function:"Disable-$($MODULE_NAME)Verbose" Export-ModuleMember -Function "Disable-$($MODULE_NAME)Verbose" function Test-MyDebug { param( - [Parameter(Position = 0)][string]$section + [Parameter(Position = 0)][string]$section, + [Parameter()][switch]$Logging ) - # Get the module debug environment variable - $moduleDebugVarName = $MODULE_NAME + "_DEBUG" - $flag = [System.Environment]::GetEnvironmentVariable($moduleDebugVarName) + function testSection($section,$flags){ + if($flags.Count -eq 0){ + return $false + } + $flags = $flags.ToLower() + $section = $section.ToLower() - # check if debugging is enabled - if ([string]::IsNullOrWhiteSpace( $flag )) { + return ($flags.Contains("all")) -or ( $flags -eq $section) + } + + + $sectionsString = get-DebugSections + + # No configuration means no debug + if([string]::IsNullOrWhiteSpace( $sectionsString )) { return $false } - $flag = $flag.ToLower() - $section = $section.ToLower() + # Get flags from sectionsString + $flags = getSectionsFromSectionsString $sectionsString + + # Add all if allow is empty. + # This mean stat flagsString only contains filters. + $flags.allow = $flags.allow.Count -eq 0 ? @("all") : $flags.allow + + # Get the module debug environment variable + $isAllow = testSection -Section:$section -Flags:$flags.allow + $isFiltered = testSection -Section:$section -Flags:$flags.filter + + $trace = $isAllow -and -not $isFiltered - $trace = ($flag -like '*all*') -or ( $section -like "*$flag*") return $trace } function Enable-ModuleNameDebug{ param( - [Parameter(Position = 0)][string]$section + [Parameter(Position = 0)][string[]]$Sections, + [Parameter()][string[]]$AddSections, + [Parameter()][string]$LoggingFilePath ) - if( [string]::IsNullOrWhiteSpace( $section )) { - $flag = "all" - } else { - $flag = $section + # Check if logging file path is provided + if( -Not ( [string]::IsNullOrWhiteSpace( $LoggingFilePath )) ) { + if(Test-Path -Path $LoggingFilePath -PathType Leaf){ + set-LogFile $LoggingFilePath + } else { + Write-Error "Logging file path '$LoggingFilePath' does not exist. Debug logging will not be enabled." + return + } } - $moduleDebugVarName = $MODULE_NAME + "_DEBUG" - [System.Environment]::SetEnvironmentVariable($moduleDebugVarName, $flag) + $sectionsString = $sections -join " " + $addedFlagsString = $AddSections -join " " + + # if no section get value from env and is still mepty set to all + if([string]::IsNullOrWhiteSpace( $sectionsString )) { + $sectionsString = get-DebugSections + if( [string]::IsNullOrWhiteSpace( $sectionsString )) { + $sectionsString = "all" + } + } + + # Add added to sectionsString if provided + if(-Not [string]::IsNullOrWhiteSpace( $addedFlagsString )) { + $sectionsString += " " + $addedFlagsString + } + + set-DebugSections $sectionsString + } Copy-Item -path Function:Enable-ModuleNameDebug -Destination Function:"Enable-$($MODULE_NAME)Debug" Export-ModuleMember -Function "Enable-$($MODULE_NAME)Debug" +function getSectionsFromSectionsString($sectionsString){ + $sections = @{ + allow = $null + filter = $null + } + + if([string]::IsNullOrWhiteSpace($sectionsString) ){ + $sections.allow = @("all") + return $sections + } + + $list = $sectionsString.Split(" ", [StringSplitOptions]::RemoveEmptyEntries) + + $split = @($list).Where({ $_ -like '-*' }, 'Split') + + $sections.filter = $split[0] | ForEach-Object { $_ -replace '^-', '' } # -> API, Auth + $sections.allow = $split[1] # -> Sync, Cache + + return $sections +} + function Disable-ModuleNameDebug { param() - $moduleDebugVarName = $MODULE_NAME + "_DEBUG" - [System.Environment]::SetEnvironmentVariable($moduleDebugVarName, $null) + set-DebugSections $null + set-LogFile $null } Copy-Item -path Function:Disable-ModuleNameDebug -Destination Function:"Disable-$($MODULE_NAME)Debug" Export-ModuleMember -Function "Disable-$($MODULE_NAME)Debug" +function Get-ModuleNameDebug { + [cmdletbinding()] + param() + + return @{ + Sections = get-DebugSections + LoggingFilePath = get-DebugLogFile + } +} +Copy-Item -path Function:Get-ModuleNameDebug -Destination Function:"Get-$($MODULE_NAME)Debug" +Export-ModuleMember -Function "Get-$($MODULE_NAME)Debug" + function Get-ObjetString { param( [Parameter(ValueFromPipeline, Position = 0)][object]$Object @@ -203,3 +316,63 @@ function Get-ObjetString { return $Object | ConvertTo-Json -Depth 10 -ErrorAction SilentlyContinue } } + +function get-Sections(){ + $moduleDebugVarName = $MODULE_NAME + "_DEBUG" + $sections = [System.Environment]::GetEnvironmentVariable($moduleDebugVarName) + + return $sections +} + +function set-Sections($sections){ + $moduleDebugVarName = $MODULE_NAME + "_DEBUG" + [System.Environment]::SetEnvironmentVariable($moduleDebugVarName, $sections) +} + +function get-LogFile(){ + $moduleDEbugLoggingVarName = $MODULE_NAME + "_DEBUG_LOGGING_FILEPATH" + $logfile = [System.Environment]::GetEnvironmentVariable($moduleDEbugLoggingVarName) + + return $logfile +} + +function set-LogFile($logFilePath){ + $moduleDEbugLoggingVarName = $MODULE_NAME + "_DEBUG_LOGGING_FILEPATH" + [System.Environment]::SetEnvironmentVariable($moduleDEbugLoggingVarName, $logFilePath) +} + +function get-DebugSections(){ + $moduleDebugVarName = $MODULE_NAME + "_DEBUG" + $sections = [System.Environment]::GetEnvironmentVariable($moduleDebugVarName) + + return $sections +} + +function set-DebugSections($sections){ + $moduleDebugVarName = $MODULE_NAME + "_DEBUG" + [System.Environment]::SetEnvironmentVariable($moduleDebugVarName, $sections) +} + +function get-DebugLogFile(){ + $moduleDEbugLoggingVarName = $MODULE_NAME + "_DEBUG_LOGGING_FILEPATH" + $logfile = [System.Environment]::GetEnvironmentVariable($moduleDEbugLoggingVarName) + + return $logfile +} + +function set-LogFile($logFilePath){ + $moduleDEbugLoggingVarName = $MODULE_NAME + "_DEBUG_LOGGING_FILEPATH" + [System.Environment]::SetEnvironmentVariable($moduleDEbugLoggingVarName, $logFilePath) +} + +function get-VerboseSections{ + $moduleVerboseVarName = $MODULE_NAME + "_VERBOSE" + $sections = [System.Environment]::GetEnvironmentVariable($moduleVerboseVarName) + + return $sections +} + +function set-VerboseSections($sections){ + $moduleVerboseVarName = $MODULE_NAME + "_VERBOSE" + [System.Environment]::SetEnvironmentVariable($moduleVerboseVarName, $sections) +} \ No newline at end of file diff --git a/private/groupAttendeesByCompany.ps1 b/private/groupAttendeesByCompany.ps1 new file mode 100644 index 0000000..d158852 --- /dev/null +++ b/private/groupAttendeesByCompany.ps1 @@ -0,0 +1,64 @@ + +function groupAttendeesByCompany { + [CmdletBinding()] + param( + [Parameter(Mandatory)][object[]]$Attendees + ) + + $validAttendees = $Attendees | Where-Object { $null -ne $_ } + + if ($validAttendees.Count -eq 0) { + return "" + } + + # Response sort order and emoji indicators per attendance type + $responseOrder = @{ + 'Accepted' = 1 + 'Tentative' = 2 + 'Following' = 3 + 'Declined' = 4 + "Didn't respond" = 5 + } + + $requiredEmoji = @{ + 'Accepted' = '✅' + 'Tentative' = '⬜️' + 'Following' = '👀' + 'Declined' = '❌' + "Didn't respond" = '⭕️' + } + + $optionalEmoji = @{ + 'Accepted' = '✅' + 'Tentative' = '🟩' + 'Following' = '👀' + 'Declined' = '❎' + "Didn't respond" = '🟩' + } + + # Group by company, sort companies alphabetically + $grouped = $validAttendees | Group-Object -Property Company | Sort-Object -Property Name + + $result = @() + + foreach ($companyGroup in $grouped) { + $memberCount = $companyGroup.Group.Count + $result += "- $($companyGroup.Name) ($memberCount)" + + # Sort by attendance (Required before Optional), then response order, then name + $sortedMembers = $companyGroup.Group | Sort-Object -Property @( + @{ Expression = { if ($_.Attendance -eq 'Required') { 0 } else { 1 } } }, + @{ Expression = { $responseOrder[$_.Response] ?? 99 } }, + @{ Expression = { ($_.DisplayName.Trim('"')).ToLower() } } + ) + + foreach ($member in $sortedMembers) { + $emojiMap = if ($member.Attendance -eq 'Required') { $requiredEmoji } else { $optionalEmoji } + $emoji = $emojiMap[$member.Response] ?? '❓' + $attLetter = if ($member.Attendance -eq 'Required') { 'R' } else { 'O' } + $result += " - $emoji$attLetter $($member.OriginalFormat)" + } + } + + return ($result -join "`n") +} diff --git a/private/parseMemberEmail.ps1 b/private/parseMemberEmail.ps1 index 4597921..059c09f 100644 --- a/private/parseMemberEmail.ps1 +++ b/private/parseMemberEmail.ps1 @@ -20,6 +20,11 @@ function parseMemberEmail { $displayName = $matches[1].Trim() $email = $matches[2].Trim() + # Filter out resource/room calendar entries + if ($email -match '@resource\.calendar\.google\.com$') { + return $null + } + # Extract domain from email $domain = ($email -split '@')[1] if ($domain) { @@ -37,11 +42,29 @@ function parseMemberEmail { $company = "Unknown" } + # If display name equals email (Outlook pattern), treat as email-only + if ($displayName -ieq $email) { + return [PSCustomObject]@{ + DisplayName = $email + Email = $email + Company = $company + OriginalFormat = $email + } + } + + # Convert ALL CAPS names to Title Case + $originalFormat = $memberString + $lettersOnly = $displayName -replace '[^a-zA-ZÀ-ÿ]', '' + if ($lettersOnly.Length -gt 1 -and $lettersOnly -ceq $lettersOnly.ToUpper()) { + $displayName = (Get-Culture).TextInfo.ToTitleCase($displayName.ToLower()) + $originalFormat = "$displayName <$email>" + } + return [PSCustomObject]@{ DisplayName = $displayName Email = $email Company = $company - OriginalFormat = $memberString + OriginalFormat = $originalFormat } } diff --git a/public/convertMeetingAttendeesToMarkdown.ps1 b/public/convertMeetingAttendeesToMarkdown.ps1 new file mode 100644 index 0000000..b13640a --- /dev/null +++ b/public/convertMeetingAttendeesToMarkdown.ps1 @@ -0,0 +1,59 @@ + +function Convert-NotesMeetingAttendeesToMarkdown { + [CmdletBinding()] + param( + [Parameter(Position = 0, ValueFromPipeline)][string]$MeetingAttendees, + [Parameter()][switch]$SetClipboard + ) + + process { + if ([string]::IsNullOrWhiteSpace($MeetingAttendees)) { + return "" + } + + # Split by newlines, trim, and filter empty lines + $lines = $MeetingAttendees -split "`n" | ForEach-Object { $_.Trim() } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } + + if ($lines.Count -lt 2) { + return "" + } + + # Skip header line (Name, Attendance, Response) + $dataLines = $lines | Select-Object -Skip 1 + + # Pattern: Name part (greedy), then Required/Optional, then response text + # Works with tabs, spaces, or no separator between fields + $linePattern = '^(.+)(Required|Optional)\s*(.+)\s*$' + + $parsedMembers = @() + + foreach ($line in $dataLines) { + if ($line -match $linePattern) { + $memberString = $matches[1].Trim() + $attendance = $matches[2] + $response = $matches[3].Trim() + + $member = parseMemberEmail -MemberString $memberString + + if ($null -ne $member) { + $member | Add-Member -NotePropertyName 'Attendance' -NotePropertyValue $attendance + $member | Add-Member -NotePropertyName 'Response' -NotePropertyValue $response + $parsedMembers += $member + } + } + } + + if ($parsedMembers.Count -eq 0) { + return "" + } + + # Group by company, sub-group by attendance, sort by response + $result = groupAttendeesByCompany -Attendees $parsedMembers + + if ($SetClipboard) { + $result | Set-Clipboard + } else { + $result + } + } +} Export-ModuleMember -Function 'Convert-NotesMeetingAttendeesToMarkdown' diff --git a/public/convertMeetingMembersToMarkdown.ps1 b/public/convertMeetingMembersToMarkdown.ps1 index 6f54795..8097a98 100644 --- a/public/convertMeetingMembersToMarkdown.ps1 +++ b/public/convertMeetingMembersToMarkdown.ps1 @@ -11,35 +11,40 @@ function Convert-NotesMeetingMembersToMarkdown { return "" } - # Parse the comma-separated list, handling quoted names with commas - # Split on ', ' followed by a quote or a letter (not inside quotes) - $members = @() - $currentMember = "" - $inQuotes = $false - - for ($i = 0; $i -lt $MeetingMembers.Length; $i++) { - $char = $MeetingMembers[$i] + # Detect format: semicolons = Outlook/Office 365, commas = Google Calendar + if ($MeetingMembers -match ';') { + # Outlook/Office 365 format: semicolon-separated + $members = $MeetingMembers -split '\s*;\s*' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } + } else { + # Google Calendar format: comma-separated with quote handling + $members = @() + $currentMember = "" + $inQuotes = $false - if ($char -eq '"') { - $inQuotes = -not $inQuotes - $currentMember += $char - } - elseif ($char -eq ',' -and -not $inQuotes) { - # End of member - if (-not [string]::IsNullOrWhiteSpace($currentMember)) { - $members += $currentMember.Trim() + for ($i = 0; $i -lt $MeetingMembers.Length; $i++) { + $char = $MeetingMembers[$i] + + if ($char -eq '"') { + $inQuotes = -not $inQuotes + $currentMember += $char + } + elseif ($char -eq ',' -and -not $inQuotes) { + # End of member + if (-not [string]::IsNullOrWhiteSpace($currentMember)) { + $members += $currentMember.Trim() + } + $currentMember = "" + } + else { + $currentMember += $char } - $currentMember = "" } - else { - $currentMember += $char + + # Add the last member + if (-not [string]::IsNullOrWhiteSpace($currentMember)) { + $members += $currentMember.Trim() } } - - # Add the last member - if (-not [string]::IsNullOrWhiteSpace($currentMember)) { - $members += $currentMember.Trim() - } # Parse each member $parsedMembers = $members | ForEach-Object { parseMemberEmail -MemberString $_ } diff --git a/public/getnoteLink.ps1 b/public/getnoteLink.ps1 index 4cd4ba5..95b5498 100644 --- a/public/getnoteLink.ps1 +++ b/public/getnoteLink.ps1 @@ -2,7 +2,9 @@ function Get-NoteLink { [CmdletBinding()] param ( # Local file path to get the GitHub link for - [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName,Position=0)][Alias("Path")][string] $NotePath + [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName,Position=0)] + [Alias("Path","FullName")][string] $NotePath, + [Parameter()][switch] $SetClipboard ) # Resolve the full path @@ -53,6 +55,10 @@ function Get-NoteLink { # Construct the GitHub URL $githubUrl = "$baseUrl/blob/$branch/$relativePath" - return $githubUrl + if ($SetClipboard) { + $githubUrl | Set-Clipboard + } else { + $githubUrl + } } Export-ModuleMember -Function Get-NoteLink diff --git a/public/newNotes.ps1 b/public/newNotes.ps1 index 10ad17d..18d7c2d 100644 --- a/public/newNotes.ps1 +++ b/public/newNotes.ps1 @@ -111,8 +111,9 @@ function getFileName{ $fullTitle = "{0}-{1}-{2}" -f $Date, $header, $Title } - # Normilize fullTitle by removing special characters and replacing spaces with underscores - $fullTitle = $fullTitle -replace '\s+', '_' + # Normalize fullTitle by replacing spaces with underscores and removing special characters + $fullTitle = $fullTitle -replace ' ', '_' + $fullTitle = $fullTitle -replace '[^\w\-]', '' return $fullTitle }