Class: Ace::Git::Worktree::Atoms::SlugGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/git/worktree/atoms/slug_generator.rb

Overview

Slug generation atom

Converts task titles and other text into URL-safe slugs suitable for git branch names and directory names.

Examples:

Generate a slug from a task title

SlugGenerator.from_title("Fix authentication bug in login flow")
# => "fix-authentication-bug-in-login-flow"

Generate a slug with custom max length

SlugGenerator.from_title("Very long task title...", max_length: 20)
# => "very-long-task-title"

Constant Summary collapse

DEFAULT_MAX_LENGTH =

Default maximum length for slugs

50
MIN_LENGTH =

Minimum length to avoid empty or too-short slugs

3
FORBIDDEN_CHARS =

Characters that are not allowed in git branch names

/[~\^*?\[\]:]/
SEPARATORS =

Characters to replace with hyphens

/[ ._\/\\()]+/
MULTIPLE_SEPARATORS =

Multiple consecutive non-word characters (but allow single hyphens)

/-{2,}|[^a-zA-Z0-9-]+/
TRIM_SEPARATORS =

Leading/trailing separators and hyphens

/^[-_]+|[-_]+$/

Class Method Summary collapse

Class Method Details

.combined(task_id, title, max_length: DEFAULT_MAX_LENGTH) ⇒ String

Generate a combined slug from task ID and title

Examples:

SlugGenerator.combined("081", "Fix authentication bug")
# => "081-fix-authentication-bug"

Parameters:

  • task_id (String)

    Task ID

  • title (String)

    Task title

  • max_length (Integer) (defaults to: DEFAULT_MAX_LENGTH)

    Maximum length of the combined slug

Returns:

  • (String)

    Combined slug like “081-fix-authentication-bug”



119
120
121
122
123
124
# File 'lib/ace/git/worktree/atoms/slug_generator.rb', line 119

def combined(task_id, title, max_length: DEFAULT_MAX_LENGTH)
  task_slug = from_task_id(task_id).gsub(/^task-/, "")
  title_slug = from_title(title, max_length: max_length - task_slug.length - 1)

  "#{task_slug}-#{title_slug}"
end

.from_task_id(task_id) ⇒ String

Generate a slug for a task ID

Examples:

SlugGenerator.from_task_id("081") # => "task-081"
SlugGenerator.from_task_id("v.0.9.0+081") # => "task-081"

Parameters:

  • task_id (String)

    Task ID (e.g., “081”, “task.081”, “v.0.9.0+081”)

Returns:

  • (String)

    Simple task ID slug



99
100
101
102
103
104
105
106
107
# File 'lib/ace/git/worktree/atoms/slug_generator.rb', line 99

def from_task_id(task_id)
  return "task" if task_id.nil? || task_id.empty?

  # Extract the numeric part from various task ID formats
  numeric_part = task_id.to_s.match(/(\d+)$/)
  return "task-#{task_id}" unless numeric_part

  "task-#{numeric_part[1]}"
end

.from_title(title, max_length: DEFAULT_MAX_LENGTH, fallback: "task") ⇒ String

Generate a slug from a task title

Examples:

SlugGenerator.from_title("Fix authentication bug")
# => "fix-authentication-bug"

SlugGenerator.from_title("Add user:profile endpoint", max_length: 20)
# => "add-user-profile-end"

Parameters:

  • title (String)

    Task title to convert

  • max_length (Integer) (defaults to: DEFAULT_MAX_LENGTH)

    Maximum length of the slug

  • fallback (String) (defaults to: "task")

    Fallback string if slug is too short/empty

Returns:

  • (String)

    URL-safe slug



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/ace/git/worktree/atoms/slug_generator.rb', line 52

def from_title(title, max_length: DEFAULT_MAX_LENGTH, fallback: "task")
  return fallback if title.nil? || title.empty?

  # Convert to string and strip whitespace
  title_str = title.to_s.strip

  # Remove forbidden characters that git doesn't allow in branch names
  cleaned = title_str.gsub(FORBIDDEN_CHARS, "")

  # Replace separators (spaces, dots, underscores, slashes) with hyphens
  normalized = cleaned.gsub(SEPARATORS, "-")

  # Remove any remaining non-alphanumeric characters except hyphens
  sanitized = normalized.gsub(MULTIPLE_SEPARATORS, "-")

  # Remove leading/trailing hyphens and underscores
  trimmed = sanitized.gsub(TRIM_SEPARATORS, "")

  # Convert to lowercase
  slug = trimmed.downcase

  # Handle empty or too-short result
  if slug.length < MIN_LENGTH
    slug = fallback
  end

  # Truncate to max length, avoiding cutting in the middle of a word if possible
  if slug.length > max_length
    slug = truncate_slug(slug, max_length)
  end

  # Final validation - ensure we still have a valid slug
  if slug.empty? || slug.length < MIN_LENGTH
    slug = fallback
  end

  slug
end

.sanitize(slug) ⇒ String

Sanitize an existing slug

Examples:

SlugGenerator.sanitize("invalid@branch#name") # => "invalid-branch-name"

Parameters:

  • slug (String)

    Slug to sanitize

Returns:

  • (String)

    Sanitized slug



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/ace/git/worktree/atoms/slug_generator.rb', line 133

def sanitize(slug)
  return "task" if slug.nil? || slug.empty?

  slug_str = slug.to_s.strip

  # Remove forbidden characters
  cleaned = slug_str.gsub(FORBIDDEN_CHARS, "")

  # Replace separators with hyphens
  normalized = cleaned.gsub(SEPARATORS, "-")

  # Remove multiple consecutive separators
  sanitized = normalized.gsub(MULTIPLE_SEPARATORS, "-")

  # Trim leading/trailing separators
  trimmed = sanitized.gsub(TRIM_SEPARATORS, "")

  # Convert to lowercase
  slug = trimmed.downcase

  # Fallback if result is empty
  slug.empty? ? "task" : slug
end

.to_directory_name(branch_name) ⇒ String

Convert a branch name to a safe directory name

Sanitizes branch names for use as directory names by replacing characters that are not alphanumeric, hyphens, or underscores with hyphens. Also handles slash-separated branch names (e.g., “origin/feature/auth”).

Examples:

Simple branch name

SlugGenerator.to_directory_name("feature-branch")
# => "feature-branch"

Branch with slashes (remote or hierarchical)

SlugGenerator.to_directory_name("origin/feature/auth")
# => "origin-feature-auth"

Branch with special characters

SlugGenerator.to_directory_name("fix:bug#123")
# => "fix-bug-123"

Parameters:

  • branch_name (String)

    Branch name to convert

Returns:

  • (String)

    Directory-safe name



177
178
179
180
181
182
# File 'lib/ace/git/worktree/atoms/slug_generator.rb', line 177

def to_directory_name(branch_name)
  return "worktree" if branch_name.nil? || branch_name.empty?

  # Replace slashes and any non-alphanumeric/hyphen/underscore with hyphens
  branch_name.to_s.tr("/", "-").gsub(/[^a-zA-Z0-9\-_]/, "-")
end

.valid?(slug) ⇒ Boolean

Check if a slug is valid for git branch names

Examples:

SlugGenerator.valid?("valid-branch-name") # => true
SlugGenerator.valid?("invalid@branch") # => false

Parameters:

  • slug (String)

    Slug to validate

Returns:

  • (Boolean)

    true if slug is valid



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/ace/git/worktree/atoms/slug_generator.rb', line 192

def valid?(slug)
  return false if slug.nil? || slug.empty?
  return false if slug.length > 255 # Git branch name limit
  return false if slug.match?(FORBIDDEN_CHARS)
  return false if slug.start_with?("-") || slug.end_with?("-")
  return false if slug.include?(".")  # Dots not allowed in git branch names
  return false if slug.include?(" ")  # Spaces not allowed

  # Check for invalid git branch name patterns
  return false if slug == "HEAD"
  return false if slug.include?("..")
  return false if slug.include?("@")

  true
end