firebrew 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +127 -0
  8. data/Rakefile +6 -0
  9. data/bin/firebrew +5 -0
  10. data/firebrew.gemspec +29 -0
  11. data/lib/firebrew.rb +20 -0
  12. data/lib/firebrew/amo_api/search.rb +50 -0
  13. data/lib/firebrew/command_line.rb +140 -0
  14. data/lib/firebrew/entity.rb +39 -0
  15. data/lib/firebrew/firefox/basic_extension.rb +10 -0
  16. data/lib/firebrew/firefox/command.rb +27 -0
  17. data/lib/firebrew/firefox/extension.rb +104 -0
  18. data/lib/firebrew/firefox/profile.rb +56 -0
  19. data/lib/firebrew/runner.rb +83 -0
  20. data/lib/firebrew/version.rb +3 -0
  21. data/spec/double/firefox.rb +2 -0
  22. data/spec/firebrew/amo_api/search_spec.rb +69 -0
  23. data/spec/firebrew/command_line_spec.rb +150 -0
  24. data/spec/firebrew/entity_spec.rb +79 -0
  25. data/spec/firebrew/firefox/command_spec.rb +39 -0
  26. data/spec/firebrew/firefox/extension_spec.rb +159 -0
  27. data/spec/firebrew/firefox/profile_spec.rb +69 -0
  28. data/spec/firebrew/runner_spec.rb +142 -0
  29. data/spec/firebrew_spec.rb +7 -0
  30. data/spec/fixtures/amo_api/search/base.xml +28 -0
  31. data/spec/fixtures/amo_api/search/empty.xml +4 -0
  32. data/spec/fixtures/amo_api/search/single.xml +12 -0
  33. data/spec/fixtures/firefox/extension/extensions.v16.json +227 -0
  34. data/spec/fixtures/firefox/extension/extensions_added_hoge.v16.json +235 -0
  35. data/spec/fixtures/firefox/profile/base.ini +18 -0
  36. data/spec/fixtures/firefox/profile/empty.ini +2 -0
  37. data/spec/spec_helper.rb +13 -0
  38. 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,10 @@
1
+ require 'firebrew/entity'
2
+ require 'active_model'
3
+
4
+ module Firebrew::Firefox
5
+ class BasicExtension
6
+ include ActiveModel::Model
7
+ include Firebrew::Entity
8
+ entity_attr :name, :guid, :version, :uri
9
+ end
10
+ 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,3 @@
1
+ module Firebrew
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ puts 'Mozilla Firefox 30.0'
@@ -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