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 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
@@ -0,0 +1,9 @@
1
+
2
+ module PrismQA
3
+
4
+ class Target
5
+ attr_accessor :name # the friendly name of this target
6
+ attr_accessor :attribute # the attribute that this target can have
7
+ end
8
+
9
+ end
@@ -0,0 +1,3 @@
1
+ module PrismQA
2
+ VERSION = "0.2.0"
3
+ end
@@ -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: []