firebrew 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|