initial public release
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
package docx
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StyleProfile represents the writing style characteristics of a resume.
|
||||
type StyleProfile struct {
|
||||
ActionVerbs []string // top 20 most frequent action verbs from bullet starts
|
||||
SummaryStyle string // first 2 sentences from any "Summary" or "Profile" section
|
||||
BulletPatterns []string // first 5 words of each bullet (up to 30 bullets)
|
||||
}
|
||||
|
||||
// StyleExtractor analyzes parsed resume markdown text.
|
||||
type StyleExtractor struct {
|
||||
markdown string
|
||||
}
|
||||
|
||||
// NewStyleExtractor creates a new style extractor for the given markdown.
|
||||
func NewStyleExtractor(markdown string) *StyleExtractor {
|
||||
return &StyleExtractor{markdown: markdown}
|
||||
}
|
||||
|
||||
// Extract analyzes the markdown and returns a StyleProfile.
|
||||
func (s *StyleExtractor) Extract() StyleProfile {
|
||||
return StyleProfile{
|
||||
ActionVerbs: s.extractActionVerbs(),
|
||||
SummaryStyle: s.extractSummary(),
|
||||
BulletPatterns: s.extractBulletPatterns(),
|
||||
}
|
||||
}
|
||||
|
||||
// extractActionVerbs finds lines starting with bullet points, extracts the first word
|
||||
// (action verb), deduplicates, and returns the top 20 by frequency.
|
||||
func (s *StyleExtractor) extractActionVerbs() []string {
|
||||
lines := strings.Split(s.markdown, "\n")
|
||||
verbCount := make(map[string]int)
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
// Match lines starting with "- " or "* "
|
||||
if strings.HasPrefix(line, "- ") {
|
||||
line = strings.TrimPrefix(line, "- ")
|
||||
} else if strings.HasPrefix(line, "* ") {
|
||||
line = strings.TrimPrefix(line, "* ")
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract first word
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) > 0 {
|
||||
verb := strings.ToLower(fields[0])
|
||||
verbCount[verb]++
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by frequency (descending) and then alphabetically for stability
|
||||
type verbFreq struct {
|
||||
verb string
|
||||
count int
|
||||
}
|
||||
var sorted []verbFreq
|
||||
for verb, count := range verbCount {
|
||||
sorted = append(sorted, verbFreq{verb, count})
|
||||
}
|
||||
|
||||
sort.Slice(sorted, func(i, j int) bool {
|
||||
if sorted[i].count != sorted[j].count {
|
||||
return sorted[i].count > sorted[j].count
|
||||
}
|
||||
return sorted[i].verb < sorted[j].verb
|
||||
})
|
||||
|
||||
var result []string
|
||||
for i, vf := range sorted {
|
||||
if i >= 20 {
|
||||
break
|
||||
}
|
||||
result = append(result, vf.verb)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// extractSummary finds a heading matching Summary|Profile|About|Objective and
|
||||
// returns the next 2 non-empty lines.
|
||||
func (s *StyleExtractor) extractSummary() string {
|
||||
lines := strings.Split(s.markdown, "\n")
|
||||
headingRegex := regexp.MustCompile(`(?i)^\s*#+\s+(summary|profile|about|objective)\s*$`)
|
||||
|
||||
for i, line := range lines {
|
||||
if headingRegex.MatchString(line) {
|
||||
// Collect next 2 non-empty lines
|
||||
var summaryLines []string
|
||||
for j := i + 1; j < len(lines) && len(summaryLines) < 2; j++ {
|
||||
trimmed := strings.TrimSpace(lines[j])
|
||||
if trimmed != "" && !strings.HasPrefix(trimmed, "#") {
|
||||
summaryLines = append(summaryLines, trimmed)
|
||||
}
|
||||
}
|
||||
if len(summaryLines) > 0 {
|
||||
return strings.Join(summaryLines, " ")
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// extractBulletPatterns extracts the first 5 words of each bullet line (up to 30 bullets).
|
||||
func (s *StyleExtractor) extractBulletPatterns() []string {
|
||||
lines := strings.Split(s.markdown, "\n")
|
||||
var patterns []string
|
||||
|
||||
for _, line := range lines {
|
||||
if len(patterns) >= 30 {
|
||||
break
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
// Match lines starting with "- " or "* "
|
||||
if strings.HasPrefix(line, "- ") {
|
||||
line = strings.TrimPrefix(line, "- ")
|
||||
} else if strings.HasPrefix(line, "* ") {
|
||||
line = strings.TrimPrefix(line, "* ")
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract first 5 words
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) > 5 {
|
||||
fields = fields[:5]
|
||||
}
|
||||
if len(fields) > 0 {
|
||||
pattern := strings.Join(fields, " ")
|
||||
patterns = append(patterns, pattern)
|
||||
}
|
||||
}
|
||||
return patterns
|
||||
}
|
||||
Reference in New Issue
Block a user