prism_qa 0.2.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.
- 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: []
|