Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
AllCops:
TargetRubyVersion: 3.2
NewCops: enable
SuggestExtensions: false

Style/StringLiterals:
EnforcedStyle: single_quotes

Style/StringLiteralsInInterpolation:
EnforcedStyle: single_quotes

Style/RedundantReturn:
Enabled: false
23 changes: 12 additions & 11 deletions .solargraph.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
---
include:
- Rakefile
- Gemfile
- "*.gemspec"
- "**/*.rb"
- Rakefile
- Gemfile
- "*.gemspec"
- "**/*.rb"
exclude:
- spec/**/*
- test/**/*
- vendor/**/*
- ".bundle/**/*"
- spec/**/*
- test/**/*
- vendor/**/*
- ".bundle/**/*"
require: []
domains: []
reporters:
- rubocop
- require_not_found
- rubocop
# - require_not_found
formatter:
rubocop:
cops: safe
except: []
only: []
extra_args: []
require_paths: []
plugins: []
plugins:
- solargraph-rspec
max_files: 5000
128 changes: 128 additions & 0 deletions lib/psdk/cli/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# frozen_string_literal: true

require 'yaml'

module Psdk
module Cli
# Class holding the configuration of the Cli
class Configuration
# Filename of the project configuration
PROJECT_CONFIGURATION_FILENAME = '.psdk-cli.yml'

# Filename of the global configuration
GLOBAL_CONFIGURATION_FILENAME = File.join(Dir.home || ENV['USERPROFILE'] || '~', '.psdk-cli/config.yml')

# Create a new configuration
# @param hash [Hash] configuration hash
def initialize(hash)
hash = {} unless hash.is_a?(Hash)
# @type [String]
@studio_path = ''
# @type [Array<String>]
@project_paths = []

self.studio_path = hash[:studio_path] if hash.key?(:studio_path)
self.project_paths = hash[:project_paths] if hash.key?(:project_paths)
end

# Get the Pokémon Studio path
# @return [String]
attr_reader :studio_path

# Set the Pokémon Studio path
# @param path [String]
def studio_path=(path)
unless Dir.exist?(path) || path.empty?
puts "[Error] Invalid studio_path at `#{path}`, this path does not exists"
return
end
# TODO: add check for locating psdk-binaries
@studio_path = path
end

# Get the project paths
# @return [Array<String>]
attr_reader :project_paths

# Set the project_paths
# @param paths [Array<String>]
def project_paths=(paths)
unless paths.is_a?(Array)
puts '[Error] project_paths is not an array'
return
end
unless paths.all? { |path| path.is_a?(String) }
puts '[Error] some of the project paths are not path'
return
end

@project_paths = paths
end

def to_h
return {
studio_path: @studio_path,
project_paths: @project_paths
}
end

class << self
@global = nil
@local = nil

# Get the project path
# @return [String | nil]
attr_reader :project_path

# Get the configuration
# @param type [:global | :local]
# @return [Configuration]
def get(type)
@global ||= Configuration.new(load_hash(GLOBAL_CONFIGURATION_FILENAME))
return @global if type == :global

project_path = find_project_path
@local = nil if @project_path != project_path
@project_path = project_path
@local ||= Configuration.new(
@global.to_h.merge(load_hash(File.join(*@project_path, PROJECT_CONFIGURATION_FILENAME)))
)

return @local
end

# Save the configuration
def save
File.write(GLOBAL_CONFIGURATION_FILENAME, YAML.dump(@global.to_h))
return unless @local && @project_path

local_configuration = @local.to_h
# Delete global configuration keys
local_configuration.delete(:project_paths)

File.write(File.join(@project_path, PROJECT_CONFIGURATION_FILENAME), YAML.dump(local_configuration))
end

private

def load_hash(path)
return {} unless File.exist?(path)

return YAML.load_file(path, symbolize_names: true, freeze: true)
rescue StandardError => e
puts "[Error] failed to load configuration (#{e.message})"
puts e.backtrace
return {}
end

# Get the project path
# @return [String | nil]
def find_project_path
current_path = Dir.pwd.gsub('\\', '/').split('/')
all_options = current_path.size.downto(2).map { |i| File.join(*current_path[0...i]) }
return all_options.find { |path| File.exist?(File.join(path, 'project.studio')) }
end
end
end
end
end
167 changes: 167 additions & 0 deletions spec/psdk/cli/configuration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# frozen_string_literal: true

# rubocop:disable Metrics/BlockLength
RSpec.describe Psdk::Cli::Configuration do
before(:example) do
Psdk::Cli::Configuration.instance_eval do
@global = nil
@local = nil
end
end

it 'uses home for the global configuration path' do
# TODO: Fix for Windows if necessary
expect(Psdk::Cli::Configuration::GLOBAL_CONFIGURATION_FILENAME).to start_with(Dir.home)
expect(Psdk::Cli::Configuration::GLOBAL_CONFIGURATION_FILENAME).to end_with('/.psdk-cli/config.yml')
end

it 'loads configuration with empty hash' do
config = Psdk::Cli::Configuration.new({})

expect(config.studio_path).to eq('')
expect(config.project_paths).to eq([])
expect(config.to_h).to eq({
studio_path: '',
project_paths: []
})
end

it 'does not update invalid path for studio_path' do
config = Psdk::Cli::Configuration.new({})

expect(config).to receive(:puts).with(
'[Error] Invalid studio_path at `/dev/null/cannot_exist`, this path does not exists'
).exactly(1).time
config.studio_path = '/dev/null/cannot_exist'
expect(config.studio_path).to eq('')
end

it 'updates studio_path' do
config = Psdk::Cli::Configuration.new({})

allow(Dir).to receive(:exist?) { |filename| filename == 'tmp/studio' }
config.studio_path = 'tmp/studio'
expect(config.studio_path).to eq('tmp/studio')
end

it 'does not update project_paths if it is not a valid array' do
config = Psdk::Cli::Configuration.new({})

expect(config).to receive(:puts).with(
'[Error] project_paths is not an array'
).exactly(1).time
config.project_paths = '/dev/null/cannot_exist'
expect(config.project_paths).to eq([])
end

it 'does not update project_paths if it contains a non string value' do
config = Psdk::Cli::Configuration.new({})

expect(config).to receive(:puts).with(
'[Error] some of the project paths are not path'
).exactly(1).time
config.project_paths = ['/dev/null/cannot_exist', 0]
expect(config.project_paths).to eq([])
end

it 'update project_paths' do
config = Psdk::Cli::Configuration.new({})

config.project_paths = ['tmp/project']
expect(config.project_paths).to eq(['tmp/project'])
end

it 'loads an empty configuration if neither global or local file exists' do
expect(File).to receive(:read).exactly(0).times

global = Psdk::Cli::Configuration.get(:global)
local = Psdk::Cli::Configuration.get(:local)

expect(global.to_h).to eq(local.to_h)
expect(global.studio_path).to eq('')
expect(global.project_paths).to eq([])
expect(global.to_h).to eq({
studio_path: '',
project_paths: []
})
end

it 'loads global configuration and merge it to local' do
stub_const('Psdk::Cli::Configuration::GLOBAL_CONFIGURATION_FILENAME', 'tmp/global.yml')
allow(Dir).to receive(:exist?) { |filename| filename == 'tmp/studio' }
allow(File).to receive(:exist?) { |filename| filename == 'tmp/global.yml' }
allow(IO).to receive(:open) do |_, &block|
block.call(StringIO.new(YAML.dump({ studio_path: 'tmp/studio', project_paths: ['project_a'] })))
end

global = Psdk::Cli::Configuration.get(:global)
local = Psdk::Cli::Configuration.get(:local)
expect(global.to_h).to eq(local.to_h)
expect(global).to_not eq(local)
expect(global.to_h).to eq({
studio_path: 'tmp/studio',
project_paths: ['project_a']
})
expect(Psdk::Cli::Configuration.project_path).to eq(nil)
end

it 'loads global configuration and merge it to local while preserving local defined values' do
stub_const('Psdk::Cli::Configuration::GLOBAL_CONFIGURATION_FILENAME', 'tmp/global.yml')
allow(Dir).to receive(:exist?) { |filename| filename.start_with?('tmp/') }
allow(File).to receive(:exist?) { |filename| filename.end_with?('/project.studio') || filename.end_with?('.yml') }
allow(IO).to receive(:open) do |filename, &block|
if filename == 'tmp/global.yml'
block.call(StringIO.new(YAML.dump({ studio_path: 'tmp/studio', project_paths: ['project_a'] })))
else
block.call(StringIO.new(YAML.dump({ studio_path: 'tmp/studio_repository' })))
end
end

global = Psdk::Cli::Configuration.get(:global)
local = Psdk::Cli::Configuration.get(:local)
expect(global.to_h).to eq({
studio_path: 'tmp/studio',
project_paths: ['project_a']
})
expect(local.to_h).to eq({
studio_path: 'tmp/studio_repository',
project_paths: ['project_a']
})
expect(Psdk::Cli::Configuration.project_path).to eq(Dir.pwd)
end

it 'successfully saves the configurations' do
stub_const('Psdk::Cli::Configuration::GLOBAL_CONFIGURATION_FILENAME', 'tmp/global.yml')
allow(File).to receive(:exist?) { |filename| filename.end_with?('/project.studio') }
allow(Dir).to receive(:exist?) { |filename| filename.start_with?('tmp/') }

global = Psdk::Cli::Configuration.get(:global)
local = Psdk::Cli::Configuration.get(:local)

global.studio_path = 'tmp/studio'
local.studio_path = 'tmp/project'
local.project_paths = %w[a b c] # Never saved

expect(File).to receive(:write).with('tmp/global.yml',
"---\n:studio_path: tmp/studio\n:project_paths: []\n")
expect(File).to receive(:write).with(File.join(Dir.pwd, '.psdk-cli.yml'),
"---\n:studio_path: tmp/project\n")
Psdk::Cli::Configuration.save
end

it 'loads the project path as the path that contains project.studio' do
allow(Dir).to receive(:pwd) { '/users/user_a/documents/projects/super_game/Data/studio/maps' }
allow(Dir).to receive(:exist?) { |filename| filename == 'tmp/studio_repository' }
allow(File).to receive(:exist?) do |filename|
next filename.end_with?('/super_game/project.studio') || filename.end_with?('/.psdk-cli.yml')
end
allow(IO).to receive(:open) do |_, &block|
block.call(StringIO.new(YAML.dump({ studio_path: 'tmp/studio_repository' })))
end

local = Psdk::Cli::Configuration.get(:local)
expect(local.to_h).to eq({ studio_path: 'tmp/studio_repository', project_paths: [] })
expect(Psdk::Cli::Configuration.project_path).to eq('/users/user_a/documents/projects/super_game')
end
end
# rubocop:enable Metrics/BlockLength
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'psdk/cli'
require 'psdk/cli/configuration'

RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure
Expand Down