gemologist 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d2f550a79f7fdd189abb5d648bb6328e5c3942ab
4
+ data.tar.gz: c0b9c303683bb8dbc3c48ef9df0c96b0aff61771
5
+ SHA512:
6
+ metadata.gz: 4058806a0585349523b6a111a36aabe68eddd324d1cd0ad3acb98069339e028156bf7f53ed515f94d21a9a123d91c6a9f13fe4d0d08f0bab5c9df5999173c9b2
7
+ data.tar.gz: 553c99661946febbbc11f8f0e5ab61143965a7c18ad7669fb84f7642480abecf76cc86d188d9626544301eeb73d28c514945a1e1ed67b5471514b567e2753832
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,23 @@
1
+ Metrics/ClassLength:
2
+ Max: 150
3
+
4
+ Metrics/LineLength:
5
+ Max: 100
6
+
7
+ Style/AlignHash:
8
+ EnforcedHashRocketStyle: table
9
+ EnforcedColonStyle: separator
10
+
11
+ Style/Documentation:
12
+ Enabled: false
13
+
14
+ Style/EmptyElse:
15
+ Enabled: false
16
+
17
+ Style/GuardClause:
18
+ Enabled: false
19
+
20
+ Style/RegexpLiteral:
21
+ Exclude:
22
+ - '*.gemspec'
23
+ - 'Guardfile'
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1
5
+ - 2.2
6
+ - ruby-head
7
+ script: bundle exec rake ci
8
+ sudo: false
9
+ cache: bundler
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem 'activesupport', '~> 4.2'
7
+ gem 'pry', '~> 0.10'
8
+ gem 'rake', '~> 10.0'
9
+ gem 'rspec', '~> 3.2'
10
+ gem 'rubocop', '~> 0.30'
11
+ end
12
+
13
+ group :test do
14
+ gem 'coveralls', '~> 0.8'
15
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Yuji Nakayama
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,35 @@
1
+ [![Gem Version](http://img.shields.io/gem/v/gemologist.svg?style=flat)](http://badge.fury.io/rb/gemologist)
2
+ [![Dependency Status](http://img.shields.io/gemnasium/yujinakayama/gemologist.svg?style=flat)](https://gemnasium.com/yujinakayama/gemologist)
3
+ [![Build Status](https://travis-ci.org/yujinakayama/gemologist.svg?branch=master&style=flat)](https://travis-ci.org/yujinakayama/gemologist)
4
+ [![Coverage Status](https://coveralls.io/repos/yujinakayama/gemologist/badge.svg?branch=master)](https://coveralls.io/r/yujinakayama/gemologist?branch=master)
5
+ [![Code Climate](https://img.shields.io/codeclimate/github/yujinakayama/gemologist.svg?style=flat)](https://codeclimate.com/github/yujinakayama/gemologist)
6
+
7
+ # Gemologist
8
+
9
+ **Gemologist** is a library for rewriting your Gemfile and gemspec.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'gemologist'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install gemologist
26
+
27
+ ## Usage
28
+
29
+ TODO: Write usage instructions here
30
+
31
+ ## License
32
+
33
+ Copyright (c) 2015 Yuji Nakayama
34
+
35
+ See the [LICENSE.txt](LICENSE.txt) for details.
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new do |task|
6
+ task.verbose = false
7
+ end
8
+
9
+ RuboCop::RakeTask.new
10
+
11
+ task ci: [:spec, :rubocop]
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'gemologist'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ require 'pry'
11
+ Pry.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gemologist/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'gemologist'
8
+ spec.version = Gemologist::Version.to_s
9
+ spec.authors = ['Yuji Nakayama']
10
+ spec.email = ['nkymyj@gmail.com']
11
+
12
+ spec.summary = 'A library for rewriting your Gemfile and gemspec.'
13
+ spec.description = spec.summary
14
+ spec.homepage = 'https://github.com/yujinakayama/gemologist'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.required_ruby_version = '>= 2.0.0'
23
+
24
+ spec.add_runtime_dependency 'bundler', '~> 1.7'
25
+ end
@@ -0,0 +1,4 @@
1
+ require 'gemologist/gemfile'
2
+ require 'gemologist/gemfile_lock'
3
+ require 'gemologist/gemspec'
4
+ require 'gemologist/version'
@@ -0,0 +1,71 @@
1
+ require 'gemologist/ast'
2
+
3
+ module Gemologist
4
+ class AbstractDependency
5
+ attr_reader :node, :rewriter
6
+
7
+ def self.valid_node?(node)
8
+ return false unless node.send_type?
9
+ _receiver_node, message, name_node, = *node
10
+ method_names.include?(message) && name_node.str_type?
11
+ end
12
+
13
+ def self.method_names
14
+ fail NotImplementedError
15
+ end
16
+
17
+ def initialize(node, rewriter)
18
+ fail 'Invalid node.' unless self.class.valid_node?(node)
19
+ @node = node
20
+ @rewriter = rewriter
21
+ end
22
+
23
+ def name
24
+ name_node.children.first
25
+ end
26
+
27
+ def groups
28
+ fail NotImplementedError
29
+ end
30
+
31
+ def version_specifiers
32
+ @version_specifiers ||= version_nodes.map { |node| AST.concretize(node) }.flatten
33
+ end
34
+
35
+ def version_specifiers=(*specifiers)
36
+ source = specifiers.flatten.map { |specifier| "'#{specifier}'" }.join(', ')
37
+
38
+ if version_nodes.empty?
39
+ rewriter.insert_after(name_node.loc.expression, ", #{source}")
40
+ else
41
+ rewriter.replace(version_range, source)
42
+ end
43
+ end
44
+
45
+ def options
46
+ {}
47
+ end
48
+
49
+ private
50
+
51
+ def method_name
52
+ node.children[1]
53
+ end
54
+
55
+ def name_node
56
+ node.children[2]
57
+ end
58
+
59
+ def trailing_nodes
60
+ node.children[3..-1]
61
+ end
62
+
63
+ def version_nodes
64
+ fail NotImplementedError
65
+ end
66
+
67
+ def version_range
68
+ version_nodes.first.loc.expression.join(version_nodes.last.loc.expression)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,50 @@
1
+ require 'astrolabe/builder'
2
+ require 'parser/current'
3
+
4
+ module Gemologist
5
+ class AbstractGemfile
6
+ attr_reader :path
7
+
8
+ def initialize(path)
9
+ @path = path
10
+ end
11
+
12
+ def find_dependency(name)
13
+ dependencies.find { |dep| dep.name == name }
14
+ end
15
+
16
+ def dependencies
17
+ @dependencies ||= ast.each_node.with_object([]) do |node, dependencies|
18
+ next unless dependency_class.valid_node?(node)
19
+ dependencies << dependency_class.new(node, source_rewriter)
20
+ end
21
+ end
22
+
23
+ def rewrite!
24
+ rewritten_source = source_rewriter.process
25
+ File.write(path, rewritten_source)
26
+ end
27
+
28
+ private
29
+
30
+ def dependency_class
31
+ fail NotImplementedError
32
+ end
33
+
34
+ def ast
35
+ @ast ||= begin
36
+ builder = Astrolabe::Builder.new
37
+ parser = Parser::CurrentRuby.new(builder)
38
+ parser.parse(source_buffer)
39
+ end
40
+ end
41
+
42
+ def source_rewriter
43
+ @source_rewriter ||= Parser::Source::Rewriter.new(source_buffer)
44
+ end
45
+
46
+ def source_buffer
47
+ @source_buffer ||= Parser::Source::Buffer.new(path).read
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,48 @@
1
+ require 'gemologist/runtime_value'
2
+
3
+ module Gemologist
4
+ module AST
5
+ module_function
6
+
7
+ def concretize(node) # rubocop:disable MethodLength, CyclomaticComplexity
8
+ return nil unless node
9
+
10
+ case node.type
11
+ when :true then true
12
+ when :false then false
13
+ when :nil then nil
14
+ when :int, :float, :str, :sym then node.children.first
15
+ when :irange, :erange then concretize_range(node)
16
+ when :regexp then concretize_regexp(node)
17
+ when :array then concretize_array(node)
18
+ when :hash then concretize_hash(node)
19
+ else RuntimeValue.new(node)
20
+ end
21
+ end
22
+
23
+ def concretize_range(range_node)
24
+ values = range_node.children.map { |child_node| concretize(child_node) }
25
+ Range.new(*values, range_node.type == :erange)
26
+ end
27
+
28
+ def concretize_regexp(regexp_node)
29
+ *body_nodes, regopt_node = *regexp_node
30
+ return RuntimeValue.new(regexp_node) unless body_nodes.all?(&:str_type?)
31
+ string = body_nodes.map { |str_node| str_node.children.first }.reduce(:+)
32
+ options = regopt_node.children.map(&:to_s).reduce(:+)
33
+ eval("/#{string}/#{options}") # rubocop:disable Eval
34
+ end
35
+
36
+ def concretize_array(array_node)
37
+ array_node.children.map { |child_node| concretize(child_node) }
38
+ end
39
+
40
+ def concretize_hash(hash_node)
41
+ hash_node.children.each_with_object({}) do |pair_node, hash|
42
+ key_node, value_node = *pair_node
43
+ key = concretize(key_node)
44
+ hash[key] = concretize(value_node) if key
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,13 @@
1
+ require 'gemologist/abstract_gemfile'
2
+
3
+ module Gemologist
4
+ class Gemfile < AbstractGemfile
5
+ require 'gemologist/gemfile/dependency'
6
+
7
+ private
8
+
9
+ def dependency_class
10
+ Gemfile::Dependency
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,60 @@
1
+ require 'gemologist/abstract_dependency'
2
+ require 'gemologist/util'
3
+
4
+ module Gemologist
5
+ class Gemfile
6
+ class Dependency < AbstractDependency
7
+ include Util
8
+
9
+ METHOD_NAMES = [:gem].freeze
10
+
11
+ def self.method_names
12
+ METHOD_NAMES
13
+ end
14
+
15
+ def groups
16
+ @groups ||= (groups_via_block + groups_via_option).map(&:to_sym)
17
+ end
18
+
19
+ def options
20
+ @options ||= symbolize_keys(AST.concretize(options_node) || {})
21
+ end
22
+
23
+ private
24
+
25
+ def groups_via_block
26
+ return [] unless group_node
27
+ _receiver_node, _message, *arg_nodes = *group_node
28
+ arg_nodes.map { |node| AST.concretize(node) }
29
+ end
30
+
31
+ def groups_via_option
32
+ Array(options[:group])
33
+ end
34
+
35
+ # https://github.com/bundler/bundler/blob/v1.7.11/lib/bundler/dsl.rb#L68-L70
36
+ def version_nodes
37
+ @version_nodes ||= trailing_nodes - [options_node]
38
+ end
39
+
40
+ def options_node
41
+ node = trailing_nodes.last
42
+
43
+ if node && node.hash_type?
44
+ node
45
+ else
46
+ nil
47
+ end
48
+ end
49
+
50
+ def group_node
51
+ candidates = node.each_ancestor(:block).map { |block_node| block_node.children.first }
52
+
53
+ candidates.find do |send_node|
54
+ receiver_node, message, = *send_node
55
+ receiver_node.nil? && message == :group
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,26 @@
1
+ module Gemologist
2
+ class GemfileLock
3
+ attr_reader :path
4
+
5
+ def initialize(path)
6
+ @path = path
7
+ end
8
+
9
+ def find_dependency(name)
10
+ dependencies.find { |dep| dep.name == name }
11
+ end
12
+
13
+ def dependencies
14
+ lockfile.specs
15
+ end
16
+
17
+ private
18
+
19
+ def lockfile
20
+ @lockfile ||= begin
21
+ content = File.read(path)
22
+ Bundler::LockfileParser.new(content)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ require 'gemologist/abstract_gemfile'
2
+
3
+ module Gemologist
4
+ class Gemspec < AbstractGemfile
5
+ require 'gemologist/gemspec/dependency'
6
+
7
+ private
8
+
9
+ def dependency_class
10
+ Gemspec::Dependency
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ require 'gemologist/abstract_dependency'
2
+
3
+ module Gemologist
4
+ class Gemspec
5
+ class Dependency < AbstractDependency
6
+ METHOD_NAMES = [:add_runtime_dependency, :add_development_dependency, :add_dependency].freeze
7
+
8
+ def self.method_names
9
+ METHOD_NAMES
10
+ end
11
+
12
+ def groups
13
+ case method_name
14
+ when :add_development_dependency
15
+ [:development]
16
+ else
17
+ []
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ # https://github.com/rubygems/rubygems/blob/v2.4.5/lib/rubygems/specification.rb#L449-L473
24
+ def version_nodes
25
+ trailing_nodes
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ module Gemologist
2
+ class RuntimeValue
3
+ attr_reader :node
4
+
5
+ def initialize(node)
6
+ @node = node
7
+ end
8
+
9
+ def source
10
+ @source ||= node.loc.expression.source
11
+ end
12
+
13
+ def inspect
14
+ "<#{self.class.name} #{source.inspect}>"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module Gemologist
2
+ module Util
3
+ module_function
4
+
5
+ def symbolize_keys(original_hash)
6
+ hash = original_hash.dup
7
+
8
+ original_hash.each do |key, value|
9
+ hash[key] = symbolize_keys(value) if value.is_a?(Hash)
10
+ hash[key.to_sym] = hash.delete(key) if key.is_a?(String)
11
+ end
12
+
13
+ hash
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ module Gemologist
2
+ # http://semver.org/
3
+ module Version
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ PATCH = 0
7
+
8
+ def self.to_s
9
+ [MAJOR, MINOR, PATCH].join('.')
10
+ end
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gemologist
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yuji Nakayama
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-04-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ description: A library for rewriting your Gemfile and gemspec.
28
+ email:
29
+ - nkymyj@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".gitignore"
35
+ - ".rspec"
36
+ - ".rubocop.yml"
37
+ - ".travis.yml"
38
+ - Gemfile
39
+ - LICENSE.txt
40
+ - README.md
41
+ - Rakefile
42
+ - bin/console
43
+ - bin/setup
44
+ - gemologist.gemspec
45
+ - lib/gemologist.rb
46
+ - lib/gemologist/abstract_dependency.rb
47
+ - lib/gemologist/abstract_gemfile.rb
48
+ - lib/gemologist/ast.rb
49
+ - lib/gemologist/gemfile.rb
50
+ - lib/gemologist/gemfile/dependency.rb
51
+ - lib/gemologist/gemfile_lock.rb
52
+ - lib/gemologist/gemspec.rb
53
+ - lib/gemologist/gemspec/dependency.rb
54
+ - lib/gemologist/runtime_value.rb
55
+ - lib/gemologist/util.rb
56
+ - lib/gemologist/version.rb
57
+ homepage: https://github.com/yujinakayama/gemologist
58
+ licenses:
59
+ - MIT
60
+ metadata: {}
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 2.0.0
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 2.4.6
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: A library for rewriting your Gemfile and gemspec.
81
+ test_files: []
82
+ has_rdoc: