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.
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