Skip to content

Commit 6cfc405

Browse files
authored
Merge pull request #98 from bashly-framework/update/installer
Update completions installer paths
2 parents c669296 + c34aaba commit 6cfc405

File tree

8 files changed

+77
-82
lines changed

8 files changed

+77
-82
lines changed

lib/completely/commands/generate.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def install(content)
6060
end
6161

6262
def show(content) = puts content
63-
63+
6464
def save(content)
6565
File.write output_path, content
6666
say "Saved m`#{output_path}`"

lib/completely/installer.rb

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'fileutils'
2+
13
module Completely
24
class Installer
35
class << self
@@ -38,27 +40,16 @@ def initialize(program:, script_path: nil)
3840
@script_path = script_path
3941
end
4042

41-
def target_directories
42-
@target_directories ||= %W[
43-
/usr/share/bash-completion/completions
44-
/usr/local/etc/bash_completion.d
45-
#{Dir.home}/.local/share/bash-completion/completions
46-
#{Dir.home}/.bash_completion.d
47-
]
48-
end
49-
5043
def install_command
51-
result = root_user? ? [] : %w[sudo]
52-
result + %W[cp #{script_path} #{target_path}]
44+
%W[cp #{script_path} #{target_path}]
5345
end
5446

5547
def install_command_string
5648
install_command.join ' '
5749
end
5850

5951
def uninstall_command
60-
result = root_user? ? [] : %w[sudo]
61-
result + %w[rm -f] + target_directories.map { |dir| "#{dir}/#{program}" }
52+
%W[rm -f #{target_path}]
6253
end
6354

6455
def uninstall_command_string
@@ -70,14 +61,12 @@ def target_path
7061
end
7162

7263
def install(force: false)
73-
unless completions_path
74-
raise InstallError, 'Cannot determine system completions directory'
75-
end
76-
7764
unless script_exist?
7865
raise InstallError, "Cannot find script: m`#{script_path}`"
7966
end
8067

68+
FileUtils.mkdir_p completions_path
69+
8170
if target_exist? && !force
8271
raise InstallError, "File exists: m`#{target_path}`"
8372
end
@@ -99,22 +88,20 @@ def script_exist?
9988
File.exist? script_path
10089
end
10190

102-
def root_user?
103-
Process.uid.zero?
91+
def completions_path
92+
@completions_path ||= "#{user_completions_base_dir}/completions"
10493
end
10594

106-
def completions_path
107-
@completions_path ||= begin
108-
result = nil
109-
target_directories.each do |target|
110-
if Dir.exist? target
111-
result = target
112-
break
113-
end
114-
end
95+
def user_completions_base_dir
96+
@user_completions_base_dir ||= bash_completion_user_dir || "#{data_home}/bash-completion"
97+
end
11598

116-
result
117-
end
99+
def bash_completion_user_dir
100+
ENV['BASH_COMPLETION_USER_DIR']&.split(':')&.find { |entry| !entry.empty? }
101+
end
102+
103+
def data_home
104+
ENV['XDG_DATA_HOME'] || "#{Dir.home}/.local/share"
118105
end
119106
end
120107
end

spec/approvals/cli/install/dry

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sudo cp completely.bash /usr/share/bash-completion/completions/completely-test
1+
cp completely.bash /home/USER/.local/share/bash-completion/completions/completely-test
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sudo cp <tmpfile-path> /usr/share/bash-completion/completions/completely-test
1+
cp <tmpfile-path> /home/USER/.local/share/bash-completion/completions/completely-test

spec/approvals/cli/uninstall/dry

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sudo rm -f /usr/share/bash-completion/completions/completely-test /usr/local/etc/bash_completion.d/completely-test /home/vagrant/.local/share/bash-completion/completion/completely-test /home/vagrant/.bash_completion.d/completely-test
1+
rm -f /home/USER/.local/share/bash-completion/completions/completely-test

spec/completely/commands/generate_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,16 +119,16 @@
119119
context 'with --install PROGRAM' do
120120
let(:mock_installer) do
121121
instance_double Installer,
122-
install: true,
122+
install: true,
123123
install_command_string: 'stubbed install_command_string',
124-
target_path: 'stubbed target_path'
124+
target_path: 'stubbed target_path'
125125
end
126126

127127
it 'passes the generated script to the installer' do
128128
allow(Installer).to receive(:from_string)
129129
.with(
130130
program: 'mycli',
131-
string: a_string_matching(/bash completions script/)
131+
string: a_string_matching(/bash completions script/)
132132
).and_return(mock_installer)
133133

134134
expect(mock_installer).to receive(:install)

spec/completely/commands/install_spec.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252

5353
expect { subject.execute %w[install completely-test --dry] }
5454
.to output_approval('cli/install/dry')
55+
.except(%r[/home/([^/]+)], '/home/USER')
5556
end
5657
end
5758

@@ -64,6 +65,7 @@
6465
expect { subject.execute %w[install completely-test - --dry] }
6566
.to output_approval('cli/install/stdin-dry')
6667
.except(/cp [^\s]*completely-[^\s]*/, 'cp <tmpfile-path>')
68+
.except(%r[/home/([^/]+)], '/home/USER')
6769
end
6870
end
6971

spec/completely/installer_spec.rb

Lines changed: 51 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,9 @@
44
let(:leeway) { RUBY_VERSION < '3.2.0' ? 0 : 3 }
55
let(:program) { 'completely-test' }
66
let(:script_path) { 'completions.bash' }
7-
let(:targets) { subject.target_directories.map { |dir| "#{dir}/#{program}" } }
8-
let(:install_command) do
9-
%W[sudo cp #{subject.script_path} #{subject.target_path}]
10-
end
11-
12-
let(:uninstall_command) do
13-
%w[sudo rm -f] + targets
14-
end
7+
let(:target_path) { "#{Dir.home}/.local/share/bash-completion/completions/#{program}" }
8+
let(:install_command) { %W[cp #{subject.script_path} #{subject.target_path}] }
9+
let(:uninstall_command) { %W[rm -f #{subject.target_path}] }
1510

1611
describe '::from_io' do
1712
subject { described_class.from_io program:, io: }
@@ -33,36 +28,61 @@
3328
end
3429
end
3530

36-
describe '#target_directories' do
37-
it 'returns an array of potential completion directories' do
38-
expect(subject.target_directories).to be_an Array
39-
expect(subject.target_directories.size).to eq 4
31+
describe '#target_path' do
32+
it 'returns a user-level target path' do
33+
expect(subject.target_path).to eq target_path
4034
end
41-
end
4235

43-
describe '#target_path' do
44-
it 'returns the first matching path' do
45-
expect(subject.target_path)
46-
.to eq '/usr/share/bash-completion/completions/completely-test'
36+
context 'when BASH_COMPLETION_USER_DIR is set' do
37+
around do |example|
38+
original = ENV['BASH_COMPLETION_USER_DIR']
39+
ENV['BASH_COMPLETION_USER_DIR'] = '/tmp/completely-user-dir'
40+
example.run
41+
ensure
42+
ENV['BASH_COMPLETION_USER_DIR'] = original
43+
end
44+
45+
it 'uses BASH_COMPLETION_USER_DIR/completions' do
46+
expect(subject.target_path).to eq '/tmp/completely-user-dir/completions/completely-test'
47+
end
4748
end
48-
end
4949

50-
describe '#install_command' do
51-
it 'returns a copy command as an array' do
52-
expect(subject.install_command)
53-
.to eq %w[sudo cp completions.bash /usr/share/bash-completion/completions/completely-test]
50+
context 'when XDG_DATA_HOME is set' do
51+
around do |example|
52+
original = ENV['XDG_DATA_HOME']
53+
ENV['XDG_DATA_HOME'] = '/tmp/completely-xdg'
54+
example.run
55+
ensure
56+
ENV['XDG_DATA_HOME'] = original
57+
end
58+
59+
it 'uses XDG_DATA_HOME/bash-completion/completions' do
60+
expect(subject.target_path).to eq '/tmp/completely-xdg/bash-completion/completions/completely-test'
61+
end
5462
end
5563

56-
context 'when the user is root' do
57-
it 'returns the command without sudo' do
58-
allow(subject).to receive(:root_user?).and_return true
64+
context 'when BASH_COMPLETION_USER_DIR has multiple entries' do
65+
around do |example|
66+
original = ENV['BASH_COMPLETION_USER_DIR']
67+
ENV['BASH_COMPLETION_USER_DIR'] = ':/tmp/completely-first:/tmp/completely-second'
68+
example.run
69+
ensure
70+
ENV['BASH_COMPLETION_USER_DIR'] = original
71+
end
5972

60-
expect(subject.install_command)
61-
.to eq %w[cp completions.bash /usr/share/bash-completion/completions/completely-test]
73+
it 'uses the first non-empty entry' do
74+
expect(subject.target_path).to eq '/tmp/completely-first/completions/completely-test'
6275
end
6376
end
6477
end
6578

79+
describe '#install_command' do
80+
it 'returns a copy command as an array' do
81+
expect(subject.install_command)
82+
.to eq %W[cp completions.bash #{target_path}]
83+
end
84+
end
85+
6686
describe '#install_command_string' do
6787
it 'returns the install command as a string' do
6888
expect(subject.install_command_string).to eq subject.install_command.join(' ')
@@ -71,15 +91,7 @@
7191

7292
describe '#uninstall_command' do
7393
it 'returns an rm command as an array' do
74-
expect(subject.uninstall_command).to eq %w[sudo rm -f] + targets
75-
end
76-
77-
context 'when the user is root' do
78-
it 'returns the command without sudo' do
79-
allow(subject).to receive(:root_user?).and_return true
80-
81-
expect(subject.uninstall_command).to eq %w[rm -f] + targets
82-
end
94+
expect(subject.uninstall_command).to eq %W[rm -f #{target_path}]
8395
end
8496
end
8597

@@ -95,15 +107,7 @@
95107

96108
before do
97109
allow(subject).to receive_messages(script_path: existing_file, target_path: missing_file)
98-
end
99-
100-
context 'when the completions_path cannot be found' do
101-
it 'raises an error' do
102-
allow(subject).to receive(:completions_path).and_return nil
103-
104-
expect { subject.install }.to raise_approval('installer/install-no-dir')
105-
.diff(leeway)
106-
end
110+
allow(FileUtils).to receive(:mkdir_p)
107111
end
108112

109113
context 'when the script cannot be found' do
@@ -128,6 +132,7 @@
128132
it 'proceeds to install' do
129133
allow(subject).to receive(:target_path).and_return existing_file
130134

135+
expect(FileUtils).to receive(:mkdir_p)
131136
expect(subject).to receive(:system).with(*install_command)
132137

133138
subject.install force: true
@@ -138,6 +143,7 @@
138143
it 'proceeds to install' do
139144
allow(subject).to receive(:target_path).and_return missing_file
140145

146+
expect(FileUtils).to receive(:mkdir_p)
141147
expect(subject).to receive(:system).with(*install_command)
142148

143149
subject.install

0 commit comments

Comments
 (0)