prism_qa 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/gem/lib/prism_qa/exceptions.rb +15 -0
- data/gem/lib/prism_qa/filesystem.rb +22 -0
- data/gem/lib/prism_qa/image.rb +19 -0
- data/gem/lib/prism_qa/imageset.rb +119 -0
- data/gem/lib/prism_qa/report.rb +120 -0
- data/gem/lib/prism_qa/reportset.rb +45 -0
- data/gem/lib/prism_qa/spectrum.rb +59 -0
- data/gem/lib/prism_qa/target.rb +9 -0
- data/gem/lib/prism_qa/version.rb +3 -0
- data/gem/lib/prism_qa.rb +30 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2dcb60824b2943940984a80d43a63148f8fe0781
|
4
|
+
data.tar.gz: 0c46c450d603844bdfc5cbd3c281205318633bd6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 43150f503b5d783f03b67da2d5130f9b3263a96a7544655e29eab7e78cac53056cf0f57250dc8a2031a4ecb8c8bb1a05e9f7d332c0f63dd03cc22d07362fa02d
|
7
|
+
data.tar.gz: 496ef1eec25e54a454dd17730caaad40cb51ecbeba04f90820109e96c7d885dc2c0e0e239293b0648f09b7a5e4beda6c9c4c7812465b8aaca2be51ebd1d32e60
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
# PrismQA exceptions
|
3
|
+
|
4
|
+
module PrismQA
|
5
|
+
|
6
|
+
# For enforcing the use of certain classes even though they may share a base class
|
7
|
+
class IncompatibilityError < TypeError; end
|
8
|
+
|
9
|
+
# For reporting improper extension of the PrismQA base classes (developer error)
|
10
|
+
class ImplementationError < NotImplementedError; end
|
11
|
+
|
12
|
+
# For reporting assertions that fail at runtime (logic errors in extender's code)
|
13
|
+
class OperationalError < RuntimeError; end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require_relative 'exceptions'
|
3
|
+
|
4
|
+
# return true if the other_path is deeper than or equal to the base path
|
5
|
+
# http://stackoverflow.com/a/26878510/2063546
|
6
|
+
def ancestor?(base, other_path)
|
7
|
+
base_parts = File.expand_path(base).split('/')
|
8
|
+
path_parts = File.expand_path(other_path).split('/')
|
9
|
+
path_parts[0..base_parts.size-1] == base_parts
|
10
|
+
end
|
11
|
+
|
12
|
+
# return the relative path from a document in a web root to a media element, given full paths to each
|
13
|
+
def web_relative_path(web_root, base_document, child_element)
|
14
|
+
c = File.expand_path(child_element)
|
15
|
+
r = File.expand_path(web_root)
|
16
|
+
unless ancestor?(r, c)
|
17
|
+
raise PrismQA::OperationalError, "Child element '#{c}' is not an ancestor of the web root '#{r}'"
|
18
|
+
end
|
19
|
+
base = Pathname.new (File.dirname(File.expand_path(base_document)))
|
20
|
+
elem = Pathname.new c
|
21
|
+
(elem.relative_path_from base).to_s
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
module PrismQA
|
3
|
+
|
4
|
+
# A simple container for image information
|
5
|
+
class Image
|
6
|
+
attr_accessor :path # the location on disk
|
7
|
+
attr_accessor :id # an application-specific unique identifier
|
8
|
+
attr_accessor :description # a friendly description
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
# Design images may optionally specify an attribute
|
13
|
+
class DesignImage < Image
|
14
|
+
attr_accessor :attribute
|
15
|
+
end
|
16
|
+
|
17
|
+
# App images are the same as images, but create this alias for symmetry / clarity
|
18
|
+
class AppImage < Image; end
|
19
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
|
2
|
+
module PrismQA
|
3
|
+
|
4
|
+
# Container class for sets of images
|
5
|
+
class ImageSet
|
6
|
+
|
7
|
+
attr_reader :images # the container for all the images in the set
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@images = []
|
11
|
+
@cache_valid = false # cache invalidation is so easy
|
12
|
+
end
|
13
|
+
|
14
|
+
# Safe way to add images to the container
|
15
|
+
def add image
|
16
|
+
self.allow image
|
17
|
+
# fix relative paths
|
18
|
+
image.path = File.expand_path(image.path)
|
19
|
+
@images << image
|
20
|
+
@cache_valid = false
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
# Raise an error if the image is not appropriate for this type of set
|
25
|
+
def allow image
|
26
|
+
puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Design image sets need to be able to report on the images they contain
|
32
|
+
class DesignImageSet < ImageSet
|
33
|
+
|
34
|
+
def allow image
|
35
|
+
# Ensure that image objects have an "attribute" field, among other things
|
36
|
+
raise IncompatibilityError, "Tried to add a non- DesignImage object to a DesignImageSet" unless image.is_a? DesignImage
|
37
|
+
|
38
|
+
# no duplicates allowed
|
39
|
+
if (@images.map { |i| [i.id, i.attribute] }).include? [image.id, image.attribute]
|
40
|
+
raise OperationalError, "Tried to add an image with duplicate ID '#{image.id}' and attribute '#{image.attribute}'"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Get the list of unique attributes contained by the images within
|
45
|
+
def contained_attributes
|
46
|
+
(@images.map { |i| i.attribute}).uniq
|
47
|
+
end
|
48
|
+
|
49
|
+
# cache the image attributes
|
50
|
+
def cache_image_attributes
|
51
|
+
return if @cache_valid
|
52
|
+
|
53
|
+
# make a hash -- hash[image id] = list of attributes defined for this image id
|
54
|
+
# we use this for convenience later
|
55
|
+
@attributes_by_id = {}
|
56
|
+
@images.each do |img|
|
57
|
+
proper_key = img.id.to_s
|
58
|
+
@attributes_by_id[proper_key] = [] unless @attributes_by_id.has_key? proper_key
|
59
|
+
@attributes_by_id[proper_key] << img.attribute
|
60
|
+
end
|
61
|
+
|
62
|
+
@cache_valid = true
|
63
|
+
end
|
64
|
+
|
65
|
+
# get the list of images that are valid for a particular attribute
|
66
|
+
def images_for_attribute attribute
|
67
|
+
self.cache_image_attributes
|
68
|
+
|
69
|
+
# return the pared-down list
|
70
|
+
@images.select do |img|
|
71
|
+
# this covers nil == nil and attribute == attribute
|
72
|
+
next true if img.attribute == attribute
|
73
|
+
|
74
|
+
# if there is no attribute for this image, it should be pulled in
|
75
|
+
# ... unless there's an exact match elsewhere in the set.
|
76
|
+
next true if img.attribute.nil? unless @attributes_by_id[img.id.to_s].include? attribute
|
77
|
+
|
78
|
+
false
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
# App image sets are tied to a target
|
86
|
+
class AppImageSet < ImageSet
|
87
|
+
|
88
|
+
attr_accessor :target
|
89
|
+
|
90
|
+
def allow image
|
91
|
+
# no duplicates
|
92
|
+
if (@images.map { |i| i.id}).include? image.id
|
93
|
+
raise OperationalError, "Tried to add an image with duplicate ID '#{image.id}'"
|
94
|
+
end
|
95
|
+
|
96
|
+
# App image sets don't need to worry about specific fields, but we keep it clean and symmetric.
|
97
|
+
raise IncompatibilityError, "Tried to add a DesignImage object to a non- DesignImageSet" if image.is_a? DesignImage
|
98
|
+
end
|
99
|
+
|
100
|
+
def cache_image_lookups
|
101
|
+
return if @cache_valid
|
102
|
+
|
103
|
+
@image_lookup = {}
|
104
|
+
@images.each do |img|
|
105
|
+
@image_lookup[img.id.to_s] = img
|
106
|
+
end
|
107
|
+
|
108
|
+
@cache_valid = true
|
109
|
+
end
|
110
|
+
|
111
|
+
def best_image_for(id)
|
112
|
+
self.cache_image_lookups
|
113
|
+
@image_lookup.fetch(id.to_s, nil)
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'markaby'
|
2
|
+
require_relative 'filesystem'
|
3
|
+
|
4
|
+
module PrismQA
|
5
|
+
|
6
|
+
class Report
|
7
|
+
attr_accessor :title
|
8
|
+
attr_accessor :attribute
|
9
|
+
attr_accessor :design_spectrum
|
10
|
+
attr_accessor :app_spectra
|
11
|
+
attr_accessor :web_document_root
|
12
|
+
attr_accessor :destination_path
|
13
|
+
attr_accessor :img_width_px
|
14
|
+
|
15
|
+
def css
|
16
|
+
width_string = ""
|
17
|
+
width_string = "width: #{@img_width_px}px;" unless img_width_px.nil?
|
18
|
+
%(
|
19
|
+
body {color:white; background-color:#333;}
|
20
|
+
table.comparison th {border-top: 1px solid #ccc;}
|
21
|
+
table.comparison td {padding-bottom:1ex; text-align:center;}
|
22
|
+
.masterimg, .appimg {background-color:white; #{width_string}}
|
23
|
+
.img {border:0;}
|
24
|
+
.missing {text-align:center; #{width_string}}
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
# if necessary, modify the path to be relative (for web-based reports)
|
29
|
+
def path_transform element_path
|
30
|
+
unless @web_document_root.nil?
|
31
|
+
element_path = web_relative_path(@web_document_root, @destination_path, element_path)
|
32
|
+
end
|
33
|
+
element_path
|
34
|
+
end
|
35
|
+
|
36
|
+
# render the report
|
37
|
+
def to_s
|
38
|
+
# initial calculations - get the app spectra that support the attribute we are reporting on
|
39
|
+
candidates = @app_spectra.select do |app_spectrum|
|
40
|
+
next true if @attribute.nil? # unless there is nothing in this candidate???? might be expensive to check.
|
41
|
+
|
42
|
+
app_spectrum.image_set.target.attribute == @attribute
|
43
|
+
end
|
44
|
+
design_images = @design_spectrum.image_set.images_for_attribute(@attribute)
|
45
|
+
columns = candidates.length + 1
|
46
|
+
|
47
|
+
me = self
|
48
|
+
|
49
|
+
# build html
|
50
|
+
mab = Markaby::Builder.new
|
51
|
+
mab.html do
|
52
|
+
|
53
|
+
head do
|
54
|
+
title "#{me.title} | Prism QA"
|
55
|
+
style :type => "text/css" do
|
56
|
+
me.css
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
body do
|
61
|
+
h1 me.title
|
62
|
+
if design_images.empty?
|
63
|
+
p "No input images were found."
|
64
|
+
else
|
65
|
+
table.comparison do
|
66
|
+
# print out the first row of the table -- the target names
|
67
|
+
tr do
|
68
|
+
td "Design"
|
69
|
+
candidates.each do |c|
|
70
|
+
td c.image_set.target.name
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# print out all the compared images
|
75
|
+
design_images.each do |design_image|
|
76
|
+
# title
|
77
|
+
tr do
|
78
|
+
th :colspan => columns do
|
79
|
+
a :name => design_image.description do
|
80
|
+
design_image.description
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# images
|
86
|
+
tr do
|
87
|
+
td :align => "right", :valign => "top" do
|
88
|
+
src = me.path_transform(design_image.path)
|
89
|
+
a :href => src do
|
90
|
+
img.masterimg :src => src, :alt => design_image.description
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
candidates.each do |candidate|
|
95
|
+
app_image = candidate.image_set.best_image_for(design_image.id)
|
96
|
+
if app_image.nil?
|
97
|
+
td { div.missing "#{design_image.description} on #{candidate.image_set.target.name}" }
|
98
|
+
else
|
99
|
+
td :align => "left", :valign => "top" do
|
100
|
+
div.holder do
|
101
|
+
src = me.path_transform(app_image.path)
|
102
|
+
a :href => src do
|
103
|
+
img.appimg :src => src, :alt => app_image.description
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
return mab.to_s
|
116
|
+
end
|
117
|
+
|
118
|
+
end #Report
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative 'filesystem'
|
2
|
+
|
3
|
+
module PrismQA
|
4
|
+
|
5
|
+
class ReportSet
|
6
|
+
attr_accessor :design_spectrum
|
7
|
+
attr_accessor :app_spectra
|
8
|
+
attr_accessor :title_for_attribute_fn
|
9
|
+
attr_accessor :path_for_attribute_fn
|
10
|
+
attr_accessor :web_document_root
|
11
|
+
attr_accessor :img_width_px
|
12
|
+
|
13
|
+
# Check whether the path is correct, particularly if we are making a web-based report
|
14
|
+
def allow_path path
|
15
|
+
unless @web_document_root.nil?
|
16
|
+
unless ancestor?(@web_document_root, path)
|
17
|
+
raise OperationalError, "Report #{path} is not an ancestor of the web root #{@web_document_root}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def write
|
23
|
+
@design_spectrum.image_set.contained_attributes.map do |attr|
|
24
|
+
|
25
|
+
# first check whether the destination is ok
|
26
|
+
path = @path_for_attribute_fn.call(attr)
|
27
|
+
self.allow_path path
|
28
|
+
|
29
|
+
r = Report.new
|
30
|
+
r.title = @title_for_attribute_fn.call(attr)
|
31
|
+
r.attribute = attr
|
32
|
+
r.design_spectrum = @design_spectrum
|
33
|
+
r.app_spectra = @app_spectra
|
34
|
+
r.web_document_root = @web_document_root
|
35
|
+
r.destination_path = path
|
36
|
+
r.img_width_px = @img_width_px
|
37
|
+
|
38
|
+
File.open(path, 'w') {|f| f.write(r.to_s) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
class Spectrum
|
3
|
+
|
4
|
+
attr_reader :image_set
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@image_set = nil # the image set for this spectrum
|
8
|
+
end
|
9
|
+
|
10
|
+
def load
|
11
|
+
image_set = self.fetch_image_set
|
12
|
+
self.allow_image_set image_set
|
13
|
+
@image_set = image_set
|
14
|
+
end
|
15
|
+
|
16
|
+
# implementation-specific: return a filled ImageSet
|
17
|
+
def fetch_image_set
|
18
|
+
puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
19
|
+
end
|
20
|
+
|
21
|
+
# implementation-specific: verify that an ImageSet is appropriate
|
22
|
+
def allow_image_set image_set
|
23
|
+
puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
module PrismQA
|
30
|
+
|
31
|
+
class DesignSpectrum < Spectrum
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
super
|
35
|
+
@order = [] # will hold the sorted indexes into the image set array
|
36
|
+
end
|
37
|
+
|
38
|
+
def allow_image_set image_set
|
39
|
+
raise ImplementationError, "Got a nil DesignImageSet object; was #{self.class.name} properly extended?" if image_set.nil?
|
40
|
+
|
41
|
+
# Ensure that we are only looking at design images
|
42
|
+
raise IncompatibilityError, "Tried to add a non- DesignImageSet object to DesignSpectrum" unless image_set.is_a? DesignImageSet
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
class AppSpectrum < Spectrum
|
49
|
+
|
50
|
+
def allow_image_set image_set
|
51
|
+
raise ImplementationError, "Got a nil DesignImageSet object; was #{self.class.name} properly extended?" if image_set.nil?
|
52
|
+
|
53
|
+
# Ensure that we are only looking at implementation images
|
54
|
+
raise IncompatibiltyError, "Tried to add a DesignImageSet object to AppSpectrum" if image_set.is_a? DesignImageSet
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
data/gem/lib/prism_qa.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'prism_qa/version'
|
2
|
+
|
3
|
+
require 'prism_qa/exceptions'
|
4
|
+
require 'prism_qa/target'
|
5
|
+
require 'prism_qa/image'
|
6
|
+
require 'prism_qa/imageset'
|
7
|
+
require 'prism_qa/spectrum'
|
8
|
+
require 'prism_qa/report'
|
9
|
+
require 'prism_qa/reportset'
|
10
|
+
|
11
|
+
|
12
|
+
module PrismQA
|
13
|
+
|
14
|
+
def self.report(design_spectrum, app_spectra, title_for_attribute_fn, path_for_attribute_fn, web_document_root, img_width_px)
|
15
|
+
# load source images
|
16
|
+
design_spectrum.load
|
17
|
+
app_spectra.each { |app_spectrum| app_spectrum.load }
|
18
|
+
|
19
|
+
rs = PrismQA::ReportSet.new
|
20
|
+
rs.design_spectrum = design_spectrum
|
21
|
+
rs.app_spectra = app_spectra
|
22
|
+
rs.title_for_attribute_fn = title_for_attribute_fn
|
23
|
+
rs.path_for_attribute_fn = path_for_attribute_fn
|
24
|
+
rs.web_document_root = web_document_root
|
25
|
+
rs.img_width_px = img_width_px
|
26
|
+
|
27
|
+
rs.write
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: prism_qa
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ian Katz
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-26 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.3'
|
20
|
+
- - '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.3.6
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- - '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.3.6
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rake
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ~>
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '10.0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '10.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: markaby
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.8'
|
54
|
+
- - '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 0.8.0
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ~>
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0.8'
|
64
|
+
- - '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 0.8.0
|
67
|
+
description: Prism helps you split your apps and your design document into visible
|
68
|
+
components. Its purpose is to enable designers to be an effective part of a QA
|
69
|
+
/ Continuous Integration process.
|
70
|
+
email:
|
71
|
+
- ifreecarve@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- gem/lib/prism_qa.rb
|
77
|
+
- gem/lib/prism_qa/exceptions.rb
|
78
|
+
- gem/lib/prism_qa/filesystem.rb
|
79
|
+
- gem/lib/prism_qa/image.rb
|
80
|
+
- gem/lib/prism_qa/imageset.rb
|
81
|
+
- gem/lib/prism_qa/report.rb
|
82
|
+
- gem/lib/prism_qa/reportset.rb
|
83
|
+
- gem/lib/prism_qa/spectrum.rb
|
84
|
+
- gem/lib/prism_qa/target.rb
|
85
|
+
- gem/lib/prism_qa/version.rb
|
86
|
+
homepage: http://github.com/ifreecarve/prism_qa
|
87
|
+
licenses:
|
88
|
+
- Apache 2.0
|
89
|
+
metadata: {}
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- gem/lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 2.4.6
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: Design QA tool
|
110
|
+
test_files: []
|