Class: Appydave::Tools::Dam::ProjectResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/appydave/tools/dam/project_resolver.rb

Overview

ProjectResolver - Resolve short project names to full paths

Handles:

  • FliVideo pattern: b65 → b65-guy-monroe-marketing-plan

  • Storyline pattern: boy-baker → boy-baker (exact match)

  • Pattern matching: b6* → all b60-b69 projects

Class Method Summary collapse

Class Method Details

.detect_from_pwdArray<String, String>

Detect brand and project from current directory

Returns:

  • (Array<String, String>)
    brand_key, project

    or [nil, nil]



116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/appydave/tools/dam/project_resolver.rb', line 116

def detect_from_pwd
  current = Dir.pwd

  # Check if we're inside a v-* directory
  if current =~ %r{/(v-[^/]+)/([^/]+)/?}
    brand_with_prefix = ::Regexp.last_match(1)
    project = ::Regexp.last_match(2) # Capture BEFORE normalize() which resets Regexp.last_match
    # Strip 'v-' prefix to get brand key (e.g., 'v-supportsignal' → 'supportsignal')
    brand_key = BrandResolver.normalize(brand_with_prefix)
    return [brand_key, project] if project_exists?(brand_key, project)
  end

  [nil, nil]
end

.list_projects(brand, pattern = nil) ⇒ Array<String>

List all projects for a brand

Parameters:

  • brand (String)

    Brand shortcut or full name

  • pattern (String, nil) (defaults to: nil)

    Optional filter pattern

Returns:

  • (Array<String>)

    List of project names



82
83
84
85
86
87
88
89
90
91
# File 'lib/appydave/tools/dam/project_resolver.rb', line 82

def list_projects(brand, pattern = nil)
  projects_dir = projects_directory(brand)

  glob_pattern = pattern || '*'
  Dir.glob("#{projects_dir}/#{glob_pattern}")
     .select { |path| File.directory?(path) }
     .select { |path| valid_project?(path) }
     .map { |path| File.basename(path) }
     .sort
end

.project_exists?(brand, project) ⇒ Boolean

Check if project exists in brand directory

Parameters:

  • brand (String)

    Brand key (e.g., ‘appydave’, ‘supportsignal’)

  • project (String)

    Project name

Returns:

  • (Boolean)

    true if project directory exists



135
136
137
138
# File 'lib/appydave/tools/dam/project_resolver.rb', line 135

def project_exists?(brand, project)
  project_path = Config.project_path(brand, project)
  Dir.exist?(project_path)
end

.resolve(brand, project_hint) ⇒ String+

Resolve project hint to full project name(s)

Parameters:

  • brand (String)

    Brand shortcut or full name

  • project_hint (String)

    Project name or pattern (e.g., ‘b65’, ‘boy-baker’, ‘b6*’)

Returns:

  • (String, Array<String>)

    Full project name or array of names for patterns



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/appydave/tools/dam/project_resolver.rb', line 18

def resolve(brand, project_hint)
  raise '❌ Project name is required' if project_hint.nil? || project_hint.empty?

  # Check for pattern (wildcard)
  return resolve_pattern(brand, project_hint) if project_hint.include?('*')

  # Exact match first - use Config.project_path to respect projects_subfolder
  full_path = Config.project_path(brand, project_hint)
  return project_hint if Dir.exist?(full_path)

  # FliVideo pattern: b65 → b65-*
  if project_hint =~ /^[a-z]\d+$/
    projects_dir = projects_directory(brand)
    matches = Dir.glob("#{projects_dir}/#{project_hint}-*")
                 .select { |path| File.directory?(path) }
                 .map { |path| File.basename(path) }

    case matches.size
    when 0
      raise "No project found matching '#{project_hint}' in #{Config.expand_brand(brand)}"
    when 1
      return matches.first
    else
      # Multiple matches - show and ask
      puts "⚠️  Multiple projects match '#{project_hint}':"
      matches.each_with_index do |match, idx|
        puts "  #{idx + 1}. #{match}"
      end
      print "\nSelect project (1-#{matches.size}): "
      selection = $stdin.gets.to_i
      return matches[selection - 1] if selection.between?(1, matches.size)

      raise 'Invalid selection'
    end
  end

  # No match - return as-is (will error later if doesn't exist)
  project_hint
end

.resolve_pattern(brand, pattern) ⇒ Array<String>

Resolve pattern to list of matching projects

Parameters:

  • brand (String)

    Brand shortcut or full name

  • pattern (String)

    Pattern with wildcards (e.g., ‘b6*’)

Returns:

  • (Array<String>)

    List of matching project names



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/appydave/tools/dam/project_resolver.rb', line 62

def resolve_pattern(brand, pattern)
  projects_dir = projects_directory(brand)
  matches = Dir.glob("#{projects_dir}/#{pattern}")
               .select { |path| File.directory?(path) }
               .select { |path| valid_project?(path) }
               .map { |path| File.basename(path) }
               .sort

  if matches.empty?
    raise "No projects found matching pattern '#{pattern}'\n   " \
          "Try: dam list #{brand}  # See all available projects"
  end

  matches
end

.valid_project?(project_path) ⇒ Boolean

Check if directory is a valid project

Parameters:

  • project_path (String)

    Full path to potential project directory

Returns:

  • (Boolean)

    true if valid project



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/appydave/tools/dam/project_resolver.rb', line 96

def valid_project?(project_path)
  basename = File.basename(project_path)

  # Exclude system/infrastructure directories
  excluded = %w[archived docs node_modules .git .github]
  return false if excluded.include?(basename)

  # Exclude organizational folders (for brands using projects_subfolder)
  # Including 'projects' because if it appears, it means projects_directory isn't working correctly
  organizational = %w[brand personas projects video-scripts]
  return false if organizational.include?(basename)

  # Exclude hidden and underscore-prefixed
  return false if basename.start_with?('.', '_')

  true
end