firebrew 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +127 -0
- data/Rakefile +6 -0
- data/bin/firebrew +5 -0
- data/firebrew.gemspec +29 -0
- data/lib/firebrew.rb +20 -0
- data/lib/firebrew/amo_api/search.rb +50 -0
- data/lib/firebrew/command_line.rb +140 -0
- data/lib/firebrew/entity.rb +39 -0
- data/lib/firebrew/firefox/basic_extension.rb +10 -0
- data/lib/firebrew/firefox/command.rb +27 -0
- data/lib/firebrew/firefox/extension.rb +104 -0
- data/lib/firebrew/firefox/profile.rb +56 -0
- data/lib/firebrew/runner.rb +83 -0
- data/lib/firebrew/version.rb +3 -0
- data/spec/double/firefox.rb +2 -0
- data/spec/firebrew/amo_api/search_spec.rb +69 -0
- data/spec/firebrew/command_line_spec.rb +150 -0
- data/spec/firebrew/entity_spec.rb +79 -0
- data/spec/firebrew/firefox/command_spec.rb +39 -0
- data/spec/firebrew/firefox/extension_spec.rb +159 -0
- data/spec/firebrew/firefox/profile_spec.rb +69 -0
- data/spec/firebrew/runner_spec.rb +142 -0
- data/spec/firebrew_spec.rb +7 -0
- data/spec/fixtures/amo_api/search/base.xml +28 -0
- data/spec/fixtures/amo_api/search/empty.xml +4 -0
- data/spec/fixtures/amo_api/search/single.xml +12 -0
- data/spec/fixtures/firefox/extension/extensions.v16.json +227 -0
- data/spec/fixtures/firefox/extension/extensions_added_hoge.v16.json +235 -0
- data/spec/fixtures/firefox/profile/base.ini +18 -0
- data/spec/fixtures/firefox/profile/empty.ini +2 -0
- data/spec/spec_helper.rb +13 -0
- metadata +196 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
module Firebrew
|
2
|
+
module Entity
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
extend ClassMethod
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethod
|
10
|
+
attr_accessor :attributes
|
11
|
+
|
12
|
+
def self.extended(base)
|
13
|
+
base.attributes = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def inherited(base)
|
17
|
+
base.attributes = self.attributes.clone
|
18
|
+
end
|
19
|
+
|
20
|
+
def entity_attr(*attrs)
|
21
|
+
attrs.uniq!
|
22
|
+
common = self.attributes & attrs
|
23
|
+
adding = attrs - common
|
24
|
+
self.attributes.push(*adding)
|
25
|
+
attr_accessor *adding
|
26
|
+
adding
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(rop)
|
31
|
+
self.class.attributes.each do |attr|
|
32
|
+
return false unless self.send(attr) == rop.send(attr)
|
33
|
+
end
|
34
|
+
return true
|
35
|
+
end
|
36
|
+
|
37
|
+
alias :eql? :==
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Firebrew::Firefox
|
2
|
+
class Command
|
3
|
+
class Executer
|
4
|
+
def exec(command)
|
5
|
+
[%x[#{command}], $?]
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(config={}, executer = Executer.new)
|
10
|
+
@config = config
|
11
|
+
@executer = executer
|
12
|
+
begin
|
13
|
+
result = @executer.exec('%{firefox} --version' % @config)
|
14
|
+
raise Firebrew::FirefoxCommandError unless result[0] =~ /Mozilla Firefox/
|
15
|
+
raise Firebrew::FirefoxCommandError unless result[1] == 0
|
16
|
+
rescue SystemCallError
|
17
|
+
raise Firebrew::FirefoxCommandError
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def version
|
22
|
+
return @version if @version.present?
|
23
|
+
result = @executer.exec('%{firefox} --version' % @config)[0]
|
24
|
+
@version = result.match(/[0-9.]+/)[0]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'json'
|
4
|
+
require 'firebrew/firefox/basic_extension'
|
5
|
+
|
6
|
+
module Firebrew::Firefox
|
7
|
+
class Extension < BasicExtension
|
8
|
+
class Manager
|
9
|
+
attr_reader :profile
|
10
|
+
|
11
|
+
def initialize(params={})
|
12
|
+
@profile = params[:profile]
|
13
|
+
end
|
14
|
+
|
15
|
+
def all
|
16
|
+
profile_extensions = self.fetch['addons'].find_all do |extension|
|
17
|
+
extension['location'] == 'app-profile'
|
18
|
+
end
|
19
|
+
|
20
|
+
profile_extensions.map do |extension|
|
21
|
+
Extension.new({
|
22
|
+
name: extension['defaultLocale']['name'],
|
23
|
+
guid: extension['id'],
|
24
|
+
version: extension['version'],
|
25
|
+
uri: '%s.xpi' % File.join(self.profile.path, 'extensions', extension['id'])
|
26
|
+
}, self)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def find(name)
|
31
|
+
self.all.find{|ext| ext.name == name }
|
32
|
+
end
|
33
|
+
|
34
|
+
def find!(name)
|
35
|
+
result = self.find(name)
|
36
|
+
raise Firebrew::ExtensionNotFoundError if result.nil?
|
37
|
+
result
|
38
|
+
end
|
39
|
+
|
40
|
+
def install(extension)
|
41
|
+
dir = File.join(self.profile.path, 'extensions')
|
42
|
+
FileUtils.mkdir_p dir
|
43
|
+
install_path = '%s.xpi' % File.join(dir, extension.guid)
|
44
|
+
|
45
|
+
open(extension.uri, 'rb') do |i|
|
46
|
+
open(install_path, 'wb') do |o|
|
47
|
+
o.write i.read
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
self.add(extension)
|
52
|
+
self.push
|
53
|
+
end
|
54
|
+
|
55
|
+
def uninstall(extension)
|
56
|
+
FileUtils.rm_f extension.uri
|
57
|
+
self.remove(extension)
|
58
|
+
self.push
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def data_path
|
64
|
+
path = File.join(self.profile.path, 'extensions.json')
|
65
|
+
raise Firebrew::ExtensionsFileNotFoundError unless File.exists? path
|
66
|
+
path
|
67
|
+
end
|
68
|
+
|
69
|
+
def fetch
|
70
|
+
return @data if @data.present?
|
71
|
+
@data = JSON.load(File.read(self.data_path))
|
72
|
+
end
|
73
|
+
|
74
|
+
def push
|
75
|
+
json = JSON::pretty_generate(self.fetch, allow_nan: true, max_nesting: false)
|
76
|
+
File.write(self.data_path, json)
|
77
|
+
end
|
78
|
+
|
79
|
+
def add(extension)
|
80
|
+
self.fetch['addons'].push(
|
81
|
+
'id'=> extension.guid,
|
82
|
+
'location'=> 'app-profile',
|
83
|
+
'version'=> extension.version,
|
84
|
+
'defaultLocale'=> {
|
85
|
+
'name'=> extension.name
|
86
|
+
}
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
def remove(extension)
|
91
|
+
self.fetch['addons'].delete_if{|v| v['id'] == extension.guid}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def initialize(attributes, manager)
|
96
|
+
super(attributes)
|
97
|
+
@manager = manager
|
98
|
+
end
|
99
|
+
|
100
|
+
def delete
|
101
|
+
@manager.uninstall(self)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'inifile'
|
3
|
+
require 'firebrew/firefox/extension'
|
4
|
+
|
5
|
+
module Firebrew::Firefox
|
6
|
+
class Profile
|
7
|
+
include ActiveModel::Model
|
8
|
+
|
9
|
+
class Manager
|
10
|
+
def initialize(params={})
|
11
|
+
@base_dir = params[:base_dir]
|
12
|
+
@data_file = params[:data_file] || 'profiles.ini'
|
13
|
+
raise Firebrew::ProfilesFileNotFoundError unless File.exists? self.data_path
|
14
|
+
end
|
15
|
+
|
16
|
+
def all
|
17
|
+
sections = IniFile.load(self.data_path).to_h
|
18
|
+
profiles = sections.find_all{|(name,prop)| name.match(/^Profile\d+$/)}
|
19
|
+
profiles.map do |(name,prop)|
|
20
|
+
Profile.new(
|
21
|
+
name: prop['Name'],
|
22
|
+
path: self.profile_path(prop['Path'], prop['IsRelative'] == '1'),
|
23
|
+
is_default: prop['Default'] == '1',
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def find(name)
|
29
|
+
self.all.find{|p| p.name == name }
|
30
|
+
end
|
31
|
+
|
32
|
+
def find!(name)
|
33
|
+
result = self.find(name)
|
34
|
+
raise Firebrew::ProfileNotFoundError if result.nil?
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def data_path
|
41
|
+
File.expand_path File.join(@base_dir, @data_file)
|
42
|
+
end
|
43
|
+
|
44
|
+
def profile_path(path, is_relative)
|
45
|
+
path = is_relative ? File.join(@base_dir, path) : path
|
46
|
+
File.expand_path path
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_accessor :name, :path, :is_default
|
51
|
+
|
52
|
+
def extensions
|
53
|
+
Extension::Manager.new(profile: self)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'firebrew/amo_api/search'
|
2
|
+
require 'firebrew/firefox/profile'
|
3
|
+
require 'firebrew/firefox/extension'
|
4
|
+
|
5
|
+
module Firebrew
|
6
|
+
class Runner
|
7
|
+
attr_accessor :config, :profile
|
8
|
+
|
9
|
+
def self.default_config(platform = RUBY_PLATFORM)
|
10
|
+
result = {
|
11
|
+
base_dir: ENV['FIREBREW_FIREFOX_PROFILE_BASE_DIR'],
|
12
|
+
firefox: ENV['FIREBREW_FIREFOX'],
|
13
|
+
profile: ENV['FIREBREW_FIREFOX_PROFILE'] || 'default',
|
14
|
+
}
|
15
|
+
|
16
|
+
case platform
|
17
|
+
when /darwin/ then
|
18
|
+
result[:base_dir] ||= '~/Library/Application Support/Firefox'
|
19
|
+
result[:firefox] ||= '/Applications/Firefox.app/Contents/MacOS/firefox-bin'
|
20
|
+
|
21
|
+
when /linux/ then
|
22
|
+
result[:base_dir] ||= '~/.mozilla/firefox'
|
23
|
+
result[:firefox] ||= '/usr/bin/firefox'
|
24
|
+
|
25
|
+
when /mswin(?!ce)|mingw|cygwin|bccwin/ then
|
26
|
+
result[:base_dir] ||= '~/AppData/Roming/Mozilla/Firefox'
|
27
|
+
result[:firefox] ||= 'C:/Program Files (x86)/Mozilla Firefox/firefox.exe'
|
28
|
+
end
|
29
|
+
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(config={})
|
34
|
+
self.config = self.class.default_config.merge(config)
|
35
|
+
|
36
|
+
@profile_manager = Firefox::Profile::Manager.new(
|
37
|
+
base_dir: self.config[:base_dir],
|
38
|
+
data_file: self.config[:data_file]
|
39
|
+
)
|
40
|
+
@firefox = Firefox::Command.new(self.config)
|
41
|
+
|
42
|
+
self.select_profile
|
43
|
+
end
|
44
|
+
|
45
|
+
def select_profile(name = nil)
|
46
|
+
self.profile = @profile_manager.find!(name || self.config[:profile])
|
47
|
+
end
|
48
|
+
|
49
|
+
def install(params={})
|
50
|
+
extension = self.profile.extensions.find(params[:term])
|
51
|
+
raise Firebrew::OperationAlreadyCompletedError if extension.present?
|
52
|
+
result = self.fetch_api(term: params[:term], max: 1).first
|
53
|
+
self.profile.extensions.install(result.extension)
|
54
|
+
end
|
55
|
+
|
56
|
+
def uninstall(params={})
|
57
|
+
begin
|
58
|
+
self.profile.extensions.find!(params[:term]).delete
|
59
|
+
rescue Firebrew::ExtensionNotFoundError
|
60
|
+
raise Firebrew::OperationAlreadyCompletedError
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def info(params={})
|
65
|
+
self.fetch_api(term: params[:term], max: 1).first
|
66
|
+
end
|
67
|
+
|
68
|
+
def list(params={})
|
69
|
+
self.profile.extensions.all
|
70
|
+
end
|
71
|
+
|
72
|
+
def search(params={})
|
73
|
+
self.fetch_api(term: params[:term])
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
def fetch_api(params={})
|
79
|
+
params.merge!(version: @firefox.version)
|
80
|
+
AmoApi::Search.fetch!(params)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Firebrew::AmoApi
|
4
|
+
describe Firebrew::AmoApi::Search do
|
5
|
+
before do
|
6
|
+
response = File.read("spec/fixtures/amo_api/search/#{self.fixture}")
|
7
|
+
|
8
|
+
ActiveResource::HttpMock.respond_to do |mock|
|
9
|
+
mock.get Search.path(self.params), {}, response
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
ActiveResource::HttpMock.reset!
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:params){{term: 'hoge'}}
|
18
|
+
let(:fixture){'base.xml'}
|
19
|
+
|
20
|
+
describe '::fetch(params)' do
|
21
|
+
subject{Search.fetch self.params}
|
22
|
+
|
23
|
+
it { is_expected.to be_instance_of(Array) }
|
24
|
+
it { expect(subject.size).to eq(3) }
|
25
|
+
|
26
|
+
it 'should construct objects' do
|
27
|
+
expect(subject[0].id).to eq('100')
|
28
|
+
expect(subject[0].name).to eq('hoge')
|
29
|
+
|
30
|
+
expect(subject[1].id).to eq('101')
|
31
|
+
expect(subject[1].name).to eq('hoge_fuga')
|
32
|
+
|
33
|
+
expect(subject[2].id).to eq('102')
|
34
|
+
expect(subject[2].name).to eq('hoge_hoge')
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when results were empty' do
|
38
|
+
let(:fixture){'empty.xml'}
|
39
|
+
it { is_expected.to be_instance_of(Array) }
|
40
|
+
it { expect(subject.size).to eq(0) }
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'when the number of results was one' do
|
44
|
+
let(:fixture){'single.xml'}
|
45
|
+
|
46
|
+
it { is_expected.to be_instance_of(Array) }
|
47
|
+
it { expect(subject.size).to eq(1) }
|
48
|
+
|
49
|
+
it 'should construct objects' do
|
50
|
+
expect(subject[0].id).to eq('100')
|
51
|
+
expect(subject[0].name).to eq('hoge')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '::fetch!(params)' do
|
57
|
+
subject {Search.fetch! self.params}
|
58
|
+
|
59
|
+
it { is_expected.to be_instance_of(Array) }
|
60
|
+
it { expect(subject.size).to eq(3) }
|
61
|
+
it { expect{subject}.to_not raise_error }
|
62
|
+
|
63
|
+
context 'when results were empty' do
|
64
|
+
let(:fixture){'empty.xml'}
|
65
|
+
it { expect{subject}.to raise_error(Firebrew::ExtensionNotFoundError) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module Firebrew
|
5
|
+
describe Firebrew::CommandLine do
|
6
|
+
subject do
|
7
|
+
CommandLine.new(self.args.split(/\s+/))
|
8
|
+
end
|
9
|
+
let(:args){''}
|
10
|
+
|
11
|
+
context 'when the command was invalid' do
|
12
|
+
let(:args){'invalid-command'}
|
13
|
+
it { expect{subject}.to raise_error(Firebrew::CommandLineError) }
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when the options was invalid' do
|
17
|
+
let(:args){'install --invalid-option'}
|
18
|
+
it { expect{subject}.to raise_error(Firebrew::CommandLineError) }
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#arguments()' do
|
22
|
+
subject { super().arguments }
|
23
|
+
|
24
|
+
describe 'install command' do
|
25
|
+
let(:args){'install addon-name'}
|
26
|
+
|
27
|
+
it 'should parse' do
|
28
|
+
is_expected.to eq(
|
29
|
+
command: :install,
|
30
|
+
params: {
|
31
|
+
term: 'addon-name'
|
32
|
+
},
|
33
|
+
config: {}
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with options' do
|
38
|
+
let(:args){'--base-dir=/path/to/dir install -p default addon-name --profile=test --firefox=/path/to/firefox'}
|
39
|
+
|
40
|
+
it 'should parse' do
|
41
|
+
is_expected.to eq(
|
42
|
+
command: :install,
|
43
|
+
params: {
|
44
|
+
term: 'addon-name'
|
45
|
+
},
|
46
|
+
config: {
|
47
|
+
base_dir: '/path/to/dir',
|
48
|
+
profile: 'test',
|
49
|
+
firefox: '/path/to/firefox'
|
50
|
+
}
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'uninstall command' do
|
57
|
+
let(:args){'uninstall addon-name'}
|
58
|
+
it 'should parse' do
|
59
|
+
is_expected.to eq(
|
60
|
+
command: :uninstall,
|
61
|
+
params: {
|
62
|
+
term: 'addon-name'
|
63
|
+
},
|
64
|
+
config: {}
|
65
|
+
)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'info command' do
|
70
|
+
let(:args){'info term'}
|
71
|
+
|
72
|
+
it 'should parse' do
|
73
|
+
is_expected.to eq(
|
74
|
+
command: :info,
|
75
|
+
params: {
|
76
|
+
term: 'term'
|
77
|
+
},
|
78
|
+
config: {}
|
79
|
+
)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'search command' do
|
84
|
+
let(:args){'search term'}
|
85
|
+
|
86
|
+
it 'should parse' do
|
87
|
+
is_expected.to eq(
|
88
|
+
command: :search,
|
89
|
+
params: {
|
90
|
+
term: 'term'
|
91
|
+
},
|
92
|
+
config: {}
|
93
|
+
)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe 'list command' do
|
98
|
+
let(:args){'list'}
|
99
|
+
it 'should parse' do
|
100
|
+
is_expected.to eq(
|
101
|
+
command: :list,
|
102
|
+
params: {},
|
103
|
+
config: {}
|
104
|
+
)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '::execute(&block)' do
|
110
|
+
subject do
|
111
|
+
begin
|
112
|
+
CommandLine.execute do
|
113
|
+
self.exeption
|
114
|
+
end
|
115
|
+
rescue SystemExit => e
|
116
|
+
self.io.rewind
|
117
|
+
return [e.status, self.io.read.strip]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
let(:exeption){nil}
|
122
|
+
let(:io){StringIO.new('','r+')}
|
123
|
+
|
124
|
+
before { $stderr = self.io }
|
125
|
+
after { $stderr = STDERR }
|
126
|
+
|
127
|
+
context 'when became successful' do
|
128
|
+
it { expect(subject[0]).to eq(0) }
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'when the `Firebrew::Error` was thrown' do
|
132
|
+
let(:exeption){raise Firebrew::CommandLineError, 'CommandLineError message'}
|
133
|
+
it { expect(subject[0]).to eq(7) }
|
134
|
+
it { expect(subject[1]).to eq('CommandLineError message') }
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'when the `SystemExit` was thrown' do
|
138
|
+
let(:exeption){abort 'abort message'}
|
139
|
+
it { expect(subject[0]).to eq(1) }
|
140
|
+
it { expect(subject[1]).to eq('abort message') }
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'when the unknown exception was thrown' do
|
144
|
+
let(:exeption){raise StandardError, 'StandardError message'}
|
145
|
+
it { expect(subject[0]).to eq(255) }
|
146
|
+
it { expect(subject[1]).to match(/^#<StandardError: StandardError message>/) }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|