Class: Sxn::Rules::ProjectDetector

Inherits:
Object
  • Object
show all
Defined in:
lib/sxn/rules/project_detector.rb

Overview

ProjectDetector analyzes project directories to determine their type, language, package manager, and suggests appropriate default rules for project setup.

Examples:

Basic usage

detector = ProjectDetector.new("/path/to/project")
info = detector.detect_project_info
puts "Project type: #{info[:type]}"
puts "Package manager: #{info[:package_manager]}"

rules = detector.suggest_default_rules
puts "Suggested rules: #{rules.keys}"

Constant Summary collapse

PROJECT_TYPES =

Project type definitions with their detection criteria

{
  rails: {
    files: %w[Gemfile config/application.rb],
    patterns: {
      gemfile_contains: ["rails"]
    },
    confidence: :high
  },
  ruby: {
    files: %w[Gemfile *.gemspec],
    patterns: {},
    confidence: :medium
  },
  nextjs: {
    files: %w[package.json next.config.js],
    patterns: {
      package_json_deps: ["next"]
    },
    confidence: :high
  },
  react: {
    files: %w[package.json],
    patterns: {
      package_json_deps: ["react"]
    },
    confidence: :high
  },
  nodejs: {
    files: %w[package.json],
    patterns: {
      package_json_deps: ["express", "fastify", "koa", "@types/node", "nodemon", "typescript"]
    },
    confidence: :medium_high
  },
  javascript: {
    files: %w[package.json],
    patterns: {},
    confidence: :medium
  },
  typescript: {
    files: %w[tsconfig.json *.ts],
    patterns: {},
    confidence: :high
  },
  python: {
    files: %w[requirements.txt setup.py pyproject.toml Pipfile],
    patterns: {},
    confidence: :medium
  },
  django: {
    files: %w[manage.py],
    patterns: {
      requirements_contains: ["django"]
    },
    confidence: :high
  },
  go: {
    files: %w[go.mod go.sum *.go],
    patterns: {},
    confidence: :high
  },
  rust: {
    files: %w[Cargo.toml Cargo.lock],
    patterns: {},
    confidence: :high
  }
}.freeze
PACKAGE_MANAGERS =

Package manager detection patterns

{
  bundler: {
    files: %w[Gemfile Gemfile.lock],
    command: "bundle"
  },
  npm: {
    files: %w[package-lock.json],
    command: "npm"
  },
  yarn: {
    files: %w[yarn.lock],
    command: "yarn"
  },
  pnpm: {
    files: %w[pnpm-lock.yaml],
    command: "pnpm"
  },
  pip: {
    files: %w[requirements.txt],
    command: "pip"
  },
  pipenv: {
    files: %w[Pipfile Pipfile.lock],
    command: "pipenv"
  },
  poetry: {
    files: %w[pyproject.toml poetry.lock],
    command: "poetry"
  },
  cargo: {
    files: %w[Cargo.toml Cargo.lock],
    command: "cargo"
  },
  go_mod: {
    files: %w[go.mod go.sum],
    command: "go"
  }
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project_path) ⇒ ProjectDetector

Initialize the project detector

Parameters:

  • project_path (String)

    Absolute path to the project directory



136
137
138
139
140
141
142
143
# File 'lib/sxn/rules/project_detector.rb', line 136

def initialize(project_path)
  raise ArgumentError, "Project path cannot be nil or empty" if project_path.nil? || project_path.empty?

  @project_path = File.realpath(project_path)
  validate_project_path!
rescue Errno::ENOENT
  raise ArgumentError, "Project path does not exist: #{project_path}"
end

Instance Attribute Details

#project_pathObject (readonly)

Returns the value of attribute project_path.



131
132
133
# File 'lib/sxn/rules/project_detector.rb', line 131

def project_path
  @project_path
end

Instance Method Details

#analyze_project_structureHash

Get detailed analysis of the project structure

Returns:

  • (Hash)

    Detailed project analysis



245
246
247
248
249
250
251
252
253
254
# File 'lib/sxn/rules/project_detector.rb', line 245

def analyze_project_structure
  {
    files: analyze_important_files,
    directories: analyze_directory_structure,
    dependencies: analyze_dependencies,
    configuration: analyze_configuration_files,
    scripts: analyze_scripts,
    documentation: analyze_documentation
  }
end

#detect_package_managerSymbol

Detect the package manager used by the project

Returns:

  • (Symbol)

    Detected package manager (:bundler, :npm, :yarn, etc.)



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/sxn/rules/project_detector.rb', line 197

def detect_package_manager
  PACKAGE_MANAGERS.each do |manager, criteria|
    return manager if criteria[:files].any? { |file| file_exists_in_project?(file) }
  end

  # Fallback logic for common scenarios
  if file_exists_in_project?("package.json")
    return :npm # Default to npm for Node.js projects without specific lock files
  end

  if file_exists_in_project?("Gemfile")
    return :bundler # Default to bundler for Ruby projects without lock files
  end

  :unknown
end

#detect_project_infoHash

Detect comprehensive project information

Returns:

  • (Hash)

    Project information including type, language, package manager, etc.



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/sxn/rules/project_detector.rb', line 148

def detect_project_info
  {
    type: detect_project_type,
    language: detect_primary_language,
    languages: detect_all_languages,
    package_manager: detect_package_manager,
    framework: detect_framework,
    has_docker: has_docker?,
    has_tests: has_tests?,
    has_ci: has_ci_config?,
    database: detect_database,
    sensitive_files: detect_sensitive_files,
    analysis_timestamp: Time.now.iso8601
  }
end

#detect_project_typeSymbol

Legacy method for compatibility with tests Detect the primary project type

Returns:

  • (Symbol)

    Detected project type (:rails, :nodejs, :python, etc.)



182
183
184
185
186
187
188
189
190
191
192
# File 'lib/sxn/rules/project_detector.rb', line 182

def detect_project_type
  detected_types = []

  PROJECT_TYPES.each do |type, criteria|
    confidence = calculate_type_confidence(type, criteria)
    detected_types << { type: type, confidence: confidence } if confidence.positive?
  end

  # Sort by confidence and return the highest
  detected_types.min_by { |t| -t[:confidence] }&.fetch(:type) || :unknown
end

#detect_type(path) ⇒ Symbol

Detect project type for a given path (used by ConfigManager)

Parameters:

  • path (String)

    Path to the project directory

Returns:

  • (Symbol)

    Detected project type (:rails, :nodejs, :python, etc.)



168
169
170
171
172
173
174
175
176
# File 'lib/sxn/rules/project_detector.rb', line 168

def detect_type(path)
  old_path = @project_path
  @project_path = File.realpath(path)
  result = detect_project_type
  @project_path = old_path
  result
rescue Errno::ENOENT
  :unknown
end

#suggest_default_rulesHash

Suggest default rules based on detected project characteristics

Returns:

  • (Hash)

    Suggested rules configuration



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/sxn/rules/project_detector.rb', line 217

def suggest_default_rules
  project_info = detect_project_info
  rules = {}

  # Add copy files rules based on project type
  copy_files = suggest_copy_files_rules(project_info)
  rules["copy_files"] = copy_files unless copy_files["config"]["files"] && copy_files["config"]["files"].empty?

  # Add setup commands rules based on package manager
  setup_commands = suggest_setup_commands_rules(project_info)
  unless setup_commands["config"]["commands"] && setup_commands["config"]["commands"].empty?
    rules["setup_commands"] =
      setup_commands
  end

  # Add template rules for common project documentation
  template_rules = suggest_template_rules(project_info)
  unless template_rules["config"]["templates"] && template_rules["config"]["templates"].empty?
    rules["templates"] =
      template_rules
  end

  rules
end