Almirah 0.2.4 → 0.2.5

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.
@@ -1,169 +1,173 @@
1
- require_relative "base_document"
2
-
3
- class Index < BaseDocument
4
-
5
- attr_accessor :project
6
-
7
- def initialize(project)
8
- super()
9
- @project = project
10
- @title = "Document Index"
11
- @id = "index"
12
- end
13
-
14
- def to_console
15
- puts "\e[36m" + "Index: " + @id + "\e[0m"
16
- end
17
-
18
- def to_html(output_file_path)
19
-
20
- html_rows = Array.new
21
-
22
- html_rows.append('')
23
- s = "<h1>#{@title}</h1>\n"
24
-
25
- # Specifications
26
- s = "<h2>Specifications</h2>\n"
27
- s += "<table class=\"controlled\">\n"
28
- s += "\t<thead>\n"
29
- s += "\t\t<th title=\"Document title\">Title</th>\n"
30
- s += "\t\t<th title=\"Number of Controlled Paragraphs\">Items</th>\n"
31
- s += "\t\t<th title=\"Number of Controlled Paragraphs with up-links\">Items<br>w/ Up-links</th>\n"
32
- s += "\t\t<th title=\"Number of references from other documents\">Items<br>w/ Down-links</th>\n"
33
- s += "\t\t<th title=\"Number of Controlled Paragraphs mentioned in Test Cases\">Covered <br>by Tests</th>\n"
34
- s += "\t\t<th title=\"Number of Controlled Paragraphs that have the same ID\">Duplicated<br>IDs</th>\n"
35
- s += "\t\t<th title=\"Number of Controlled Paragraphs that link to non-existing items\">Wrong<br>links</th>\n"
36
- s += "\t\t<th title=\"Number of 'TODO:' blocks in document\">TODOs</th>\n"
37
- s += "\t\t<th title=\"The last Controlled Paragraph sequence number (ID) used in the document\">Last Used<br>ID</th>\n"
38
- s += "</thead>\n"
39
- html_rows.append s
40
-
41
- sorted_items = @project.specifications.sort_by { |w| w.id }
42
-
43
- sorted_items.each do |doc|
44
- s = "\t<tr>\n"
45
- s += "\t\t<td class=\"item_text\" style='padding: 5px;'><a href=\"./specifications/#{doc.id}/#{doc.id}.html\" class=\"external\"><i class=\"fa fa-file-text-o\" style='background-color: ##{doc.color};'> </i>&nbsp#{doc.title}</a></td>\n"
46
- s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{doc.controlled_items.length.to_s}</td>\n"
47
- s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{doc.items_with_uplinks_number.to_s}</td>\n"
48
- s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{doc.items_with_downlinks_number.to_s}</td>\n"
49
- s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{doc.items_with_coverage_number.to_s}</td>\n"
50
-
51
- if doc.duplicated_ids_number >0
52
- s += "\t\t<td class=\"item_id\" style='width: 7%; background-color: #fcc;'>"
53
- s += "<div id=\"DL_#{doc.id}\" style=\"display: block;\">"
54
- s += "<a href=\"#\" onclick=\"downlink_OnClick(this.parentElement); return false;\" class=\"external\" title=\"Number of Controlled Paragraphs that have the same ID\">#{doc.duplicated_ids_number.to_s}</a>"
55
- s += "</div>"
56
- s += "<div id=\"DLS_#{doc.id}\" style=\"display: none;\">"
57
- doc.duplicates_list.each do |lnk|
58
- s += "\t\t\t<a href=\"./specifications/#{doc.id}/#{doc.id}.html##{lnk.id}\" class=\"external\" title=\"Controlled Paragraph with duplicated ID\">#{lnk.id}</a>\n<br>"
59
- end
60
- s += "</div>"
61
- s += "</td>\n"
62
- else
63
- s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{doc.duplicated_ids_number.to_s}</td>\n"
64
- end
65
-
66
- if doc.wrong_links_hash.length >0
67
- s += "\t\t<td class=\"item_id\" style='width: 7%; background-color: #fcc;'>"
68
- s += "<div id=\"DL_#{doc.id}wl\" style=\"display: block;\">"
69
- s += "<a href=\"#\" onclick=\"downlink_OnClick(this.parentElement); return false;\" class=\"external\" title=\"Number of Controlled Paragraphs that link to non-existing items\">#{doc.wrong_links_hash.length.to_s}</a>"
70
- s += "</div>"
71
- s += "<div id=\"DLS_#{doc.id}wl\" style=\"display: none;\">"
72
- doc.wrong_links_hash.each do |wrong_lnk, item|
73
- s += "\t\t\t<a href=\"./specifications/#{doc.id}/#{doc.id}.html##{item.id}\" class=\"external\" title=\"Controlled Paragraphs that link to non-existing items\">#{wrong_lnk}</a>\n<br>"
74
- end
75
- s += "</div>"
76
- s += "</td>\n"
77
- else
78
- s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{doc.wrong_links_hash.length.to_s}</td>\n"
79
- end
80
-
81
- if doc.todo_blocks.length >0
82
- color = "background-color: #fcc;"
83
- else
84
- color = ""
85
- end
86
- s += "\t\t<td class=\"item_id\" style='width: 7%; #{color}'>#{doc.todo_blocks.length.to_s}</td>\n"
87
- s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{doc.last_used_id.to_s}</td>\n"
88
- s += "</tr>\n"
89
- html_rows.append s
90
- end
91
- html_rows.append "</table>\n"
92
-
93
- # Traceability Matrices
94
- s = "<h2>Traceability Matrices</h2>\n"
95
- s += "<table class=\"controlled\">\n"
96
- s += "\t<thead>\n"
97
- s += "\t\t<th title=\"Traceability Matrix Title\">Title</th>\n"
98
- s += "\t\t<th title=\"The ratio of Controlled Paragraphs mentioned in other documents and not-mentioned ones\">Coverage</th>\n"
99
- s += "\t\t<th title=\"Document, that contains Cotroled Paragraphs to be referenced in Bottom document(s)\">Top Document</th>\n"
100
- s += "\t\t<th title=\"Document(s), that contains references to Controlled Paragraphs from the Top Document\">Bottom Document</th>\n"
101
- s += "</thead>\n"
102
- html_rows.append s
103
-
104
- sorted_items = @project.traceability_matrices.sort_by { |w| w.id }
105
- # buble-up design inputs
106
- design_inputs = []
107
- others = []
108
- sorted_items.each do |doc|
109
- if doc.bottom_doc
110
- others.append doc
111
- else
112
- design_inputs.append doc
113
- end
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_document'
4
+
5
+ class Index < BaseDocument # rubocop:disable Metrics/ClassLength,Style/Documentation
6
+ attr_accessor :project
7
+
8
+ def initialize(project)
9
+ super()
10
+ @project = project
11
+ @title = 'Document Index'
12
+ @id = 'index'
13
+ end
14
+
15
+ def to_console
16
+ puts "\e[36mIndex: #{@id}\e[0m"
17
+ end
18
+
19
+ def to_html(output_file_path) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
20
+ html_rows = []
21
+
22
+ html_rows.append('')
23
+ s = "<h1>#{@title}</h1>\n"
24
+
25
+ # Specifications
26
+ s = "<h2>Specifications</h2>\n"
27
+ s += "<table class=\"controlled\">\n"
28
+ s += "\t<thead>\n"
29
+ s += "\t\t<th title=\"Document title\">Title</th>\n"
30
+ s += "\t\t<th title=\"Number of Controlled Paragraphs\">Items</th>\n"
31
+ s += "\t\t<th title=\"Number of Controlled Paragraphs with up-links\">Items<br>w/ Up-links</th>\n"
32
+ s += "\t\t<th title=\"Number of references from other documents\">Items<br>w/ Down-links</th>\n"
33
+ s += "\t\t<th title=\"Number of Controlled Paragraphs mentioned in Test Cases\">Covered <br>by Tests</th>\n"
34
+ s += "\t\t<th title=\"Number of Controlled Paragraphs that have the same ID\">Duplicated<br>IDs</th>\n"
35
+ s += "\t\t<th title=\"Number of Controlled Paragraphs that link to non-existing items\">Wrong<br>links</th>\n"
36
+ s += "\t\t<th title=\"Number of 'TODO:' blocks in document\">TODOs</th>\n"
37
+ s += "\t\t<th title=\"The last Controlled Paragraph sequence number (ID) used in the document\">Last Used<br>ID</th>\n"
38
+ s += "</thead>\n"
39
+ html_rows.append s
40
+
41
+ sorted_items = @project.specifications.sort_by(&:id)
42
+
43
+ sorted_items.each do |doc| # rubocop:disable Metrics/BlockLength
44
+ s = "\t<tr>\n"
45
+ s += "\t\t<td class=\"item_text\" style='padding: 5px;'><a href=\"./specifications/#{doc.id}/#{doc.id}.html\" class=\"external\"><i class=\"fa fa-file-text-o\" style='background-color: ##{doc.color};'> </i>&nbsp#{doc.title}</a></td>\n"
46
+ s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{doc.controlled_items.length}</td>\n"
47
+ s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{doc.items_with_uplinks_number}</td>\n"
48
+ s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{doc.items_with_downlinks_number}</td>\n"
49
+ s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{doc.items_with_coverage_number}</td>\n"
50
+
51
+ if doc.duplicated_ids_number.positive?
52
+ s += "\t\t<td class=\"item_id\" style='width: 7%; background-color: #fcc;'>"
53
+ s += "<div id=\"DL_#{doc.id}\" style=\"display: block;\">"
54
+ s += "<a href=\"#\" onclick=\"downlink_OnClick(this.parentElement); return false;\" class=\"external\" title=\"Number of Controlled Paragraphs that have the same ID\">#{doc.duplicated_ids_number}</a>"
55
+ s += '</div>'
56
+ s += "<div id=\"DLS_#{doc.id}\" style=\"display: none;\">"
57
+ doc.duplicates_list.each do |lnk|
58
+ s += "\t\t\t<a href=\"./specifications/#{doc.id}/#{doc.id}.html##{lnk.id}\" class=\"external\" title=\"Controlled Paragraph with duplicated ID\">#{lnk.id}</a>\n<br>"
114
59
  end
115
- sorted_items = design_inputs + others
116
-
117
- sorted_items.each do |doc|
118
- s = "\t<tr>\n"
119
- coverage = doc.traced_items.length.to_f / doc.top_doc.controlled_items.length.to_f * 100.0
120
- s += "\t\t<td class=\"item_text\" style='padding: 5px;'><a href=\"./specifications/#{doc.id}/#{doc.id}.html\" class=\"external\">#{doc.title}</a></td>\n"
121
- s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{'%.2f' % coverage}%</td>\n"
122
- s += "\t\t<td class=\"item_text\" style='width: 25%; padding: 5px;'><i class=\"fa fa-file-text-o\" style='background-color: ##{doc.top_doc.color};'> </i>&nbsp#{doc.top_doc.title}</td>\n"
123
- if doc.bottom_doc
124
- s += "\t\t<td class=\"item_text\" style='width: 25%; padding: 5px;'><i class=\"fa fa-file-text-o\" style='background-color: ##{doc.bottom_doc.color};'> </i>&nbsp#{doc.bottom_doc.title}</td>\n"
125
- else
126
- s += "\t\t<td class=\"item_text\" style='width: 25%; padding: 5px;'><i class=\"fa fa-circle-o\"'> </i>&nbspAll References</td>\n"
127
- end
128
- s += "</tr>\n"
129
- html_rows.append s
60
+ s += '</div>'
61
+ s += "</td>\n"
62
+ else
63
+ s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{doc.duplicated_ids_number}</td>\n"
64
+ end
65
+
66
+ if !doc.wrong_links_hash.empty?
67
+ s += "\t\t<td class=\"item_id\" style='width: 7%; background-color: #fcc;'>"
68
+ s += "<div id=\"DL_#{doc.id}wl\" style=\"display: block;\">"
69
+ s += "<a href=\"#\" onclick=\"downlink_OnClick(this.parentElement); return false;\" class=\"external\" title=\"Number of Controlled Paragraphs that link to non-existing items\">#{doc.wrong_links_hash.length}</a>"
70
+ s += '</div>'
71
+ s += "<div id=\"DLS_#{doc.id}wl\" style=\"display: none;\">"
72
+ doc.wrong_links_hash.each do |wrong_lnk, item|
73
+ s += "\t\t\t<a href=\"./specifications/#{doc.id}/#{doc.id}.html##{item.id}\" class=\"external\" title=\"Controlled Paragraphs that link to non-existing items\">#{wrong_lnk}</a>\n<br>"
130
74
  end
131
- html_rows.append "</table>\n"
132
-
133
- # Coverage Matrices
134
- if @project.coverage_matrices.length > 0
135
- s = "<h2>Coverage Matrices</h2>\n"
136
- s += "<table class=\"controlled\">\n"
137
- s += "\t<thead>\n"
138
- s += "\t\t<th>Title</th>\n"
139
- s += "\t\t<th title=\"The ratio of Controlled Paragraphs mentioned in test protocols \
140
- and total number of Controlled Paragraphs\">Coverage</th>\n"
141
- s += "\t\t<th title=\"Numbers of passed and failed test steps\">Test Results</th>\n"
142
- s += "\t\t<th>Specification Covered</th>\n"
143
- s += "</thead>\n"
144
- html_rows.append s
145
-
146
- sorted_items = @project.coverage_matrices.sort_by { |w| w.id }
147
-
148
- sorted_items.each do |doc|
149
- s = "\t<tr>\n"
150
- coverage = doc.covered_items.length.to_f / doc.top_doc.controlled_items.length * 100.0
151
- s += "\t\t<td class=\"item_text\" style='padding: 5px;'><a href=\"./specifications/#{doc.id}/#{doc.id}.html\" class=\"external\">#{doc.title}</a></td>\n"
152
- s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{'%.2f' % coverage}%</td>\n"
153
- s += "\t\t<td class=\"item_id\" style='width: 7%;'> n/a </td>\n"
154
- s += "\t\t<td class=\"item_text\" style='width: 25%; padding: 5px;'>\
75
+ s += '</div>'
76
+ s += "</td>\n"
77
+ else
78
+ s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{doc.wrong_links_hash.length}</td>\n"
79
+ end
80
+
81
+ color = if !doc.todo_blocks.empty?
82
+ 'background-color: #fcc;'
83
+ else
84
+ ''
85
+ end
86
+ s += "\t\t<td class=\"item_id\" style='width: 7%; #{color}'>#{doc.todo_blocks.length}</td>\n"
87
+ s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{doc.last_used_id}</td>\n"
88
+ s += "</tr>\n"
89
+ html_rows.append s
90
+ end
91
+ html_rows.append "</table>\n"
92
+
93
+ # Traceability Matrices
94
+ s = "<h2>Traceability Matrices</h2>\n"
95
+ s += "<table class=\"controlled\">\n"
96
+ s += "\t<thead>\n"
97
+ s += "\t\t<th title=\"Traceability Matrix Title\">Title</th>\n"
98
+ s += "\t\t<th title=\"The ratio of Controlled Paragraphs mentioned in other documents and not-mentioned ones\">Coverage</th>\n"
99
+ s += "\t\t<th title=\"Document, that contains Cotroled Paragraphs to be referenced in Bottom document(s)\">Top Document</th>\n"
100
+ s += "\t\t<th title=\"Document(s), that contains references to Controlled Paragraphs from the Top Document\">Bottom Document</th>\n"
101
+ s += "</thead>\n"
102
+ html_rows.append s
103
+
104
+ sorted_items = @project.traceability_matrices.sort_by(&:id)
105
+ # buble-up design inputs
106
+ design_inputs = []
107
+ others = []
108
+ sorted_items.each do |doc|
109
+ if doc.bottom_doc
110
+ others.append doc
111
+ else
112
+ design_inputs.append doc
113
+ end
114
+ end
115
+ sorted_items = design_inputs + others
116
+
117
+ sorted_items.each do |doc|
118
+ s = "\t<tr>\n"
119
+ coverage = doc.traced_items.length.to_f / doc.top_doc.controlled_items.length * 100.0
120
+ s += "\t\t<td class=\"item_text\" style='padding: 5px;'><a href=\"./specifications/#{doc.id}/#{doc.id}.html\" class=\"external\">#{doc.title}</a></td>\n"
121
+ s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{'%.2f' % coverage}%</td>\n"
122
+ s += "\t\t<td class=\"item_text\" style='width: 25%; padding: 5px;'><i class=\"fa fa-file-text-o\" style='background-color: ##{doc.top_doc.color};'> </i>&nbsp#{doc.top_doc.title}</td>\n"
123
+ if doc.bottom_doc
124
+ s += "\t\t<td class=\"item_text\" style='width: 25%; padding: 5px;'><i class=\"fa fa-file-text-o\" style='background-color: ##{doc.bottom_doc.color};'> </i>&nbsp#{doc.bottom_doc.title}</td>\n"
125
+ else
126
+ s += "\t\t<td class=\"item_text\" style='width: 25%; padding: 5px;'><i class=\"fa fa-circle-o\"'> </i>&nbspAll References</td>\n"
127
+ end
128
+ s += "</tr>\n"
129
+ html_rows.append s
130
+ end
131
+ html_rows.append "</table>\n"
132
+
133
+ # Coverage Matrices
134
+ unless @project.coverage_matrices.empty?
135
+ s = "<h2>Coverage Matrices</h2>\n"
136
+ s += "<table class=\"controlled\">\n"
137
+ s += "\t<thead>\n"
138
+ s += "\t\t<th>Title</th>\n"
139
+ s += "\t\t<th title=\"The ratio of Controlled Paragraphs mentioned in test protocols and total number of Controlled Paragraphs\">Coverage</th>\n"
140
+ s += "\t\t<th title=\"Numbers of passed and failed test steps\">Test Results</th>\n"
141
+ s += "\t\t<th>Specification Covered</th>\n"
142
+ s += "</thead>\n"
143
+ html_rows.append s
144
+
145
+ sorted_items = @project.coverage_matrices.sort_by(&:id)
146
+
147
+ sorted_items.each do |doc|
148
+ s = "\t<tr>\n"
149
+ coverage = doc.covered_items.length.to_f / doc.top_doc.controlled_items.length * 100.0
150
+
151
+ test_step_color = if doc.failed_steps_number.positive?
152
+ 'background-color: #fcc;'
153
+ elsif !doc.failed_steps_number.positive? && doc.passed_steps_number.positive?
154
+ 'background-color: #cfc;'
155
+ else
156
+ 'background-color: #ffffee;'
157
+ end
158
+
159
+ s += "\t\t<td class=\"item_text\" style='padding: 5px;'><a href=\"./specifications/#{doc.id}/#{doc.id}.html\" class=\"external\">#{doc.title}</a></td>\n"
160
+ s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{'%.2f' % coverage}%</td>\n" # rubocop:disable Style/FormatString
161
+ s += "\t\t<td class=\"item_id\" style='width: 7%; #{test_step_color}' title='pass/fail'> #{doc.passed_steps_number}/#{doc.failed_steps_number} </td>\n"
162
+ s += "\t\t<td class=\"item_text\" style='width: 25%; padding: 5px;'>\
155
163
  <i class=\"fa fa-file-text-o\" style='background-color: ##{doc.top_doc.color};'> </i>\
156
164
  #{doc.top_doc.title}</td>\n"
157
- s += "</tr>\n"
158
- html_rows.append s
159
- end
160
- html_rows.append "</table>\n"
161
- end
162
-
163
- self.save_html_to_file(html_rows, nil, output_file_path)
164
-
165
+ s += "</tr>\n"
166
+ html_rows.append s
167
+ end
168
+ html_rows.append "</table>\n"
165
169
  end
166
170
 
167
-
168
-
169
- end
171
+ save_html_to_file(html_rows, nil, output_file_path)
172
+ end
173
+ end
@@ -1,20 +1,17 @@
1
- require_relative "base_document"
1
+ # frozen_string_literal: true
2
2
 
3
- class PersistentDocument < BaseDocument
3
+ require_relative 'base_document'
4
4
 
5
- attr_accessor :path
6
- attr_accessor :items
7
- attr_accessor :controlled_items
8
- attr_accessor :headings
9
- attr_accessor :up_link_docs
5
+ class PersistentDocument < BaseDocument # rubocop:disable Style/Documentation
6
+ attr_accessor :path, :items, :controlled_items, :headings, :up_link_docs, :frontmatter
10
7
 
11
- def initialize(fele_path)
12
- super()
13
- @path = fele_path
14
- @items = []
15
- @controlled_items = []
16
- @headings = []
17
- @up_link_docs = {}
18
- end
19
-
20
- end
8
+ def initialize(fele_path)
9
+ super()
10
+ @path = fele_path
11
+ @items = []
12
+ @controlled_items = []
13
+ @headings = []
14
+ @up_link_docs = {}
15
+ @frontmatter = nil
16
+ end
17
+ end
@@ -32,6 +32,8 @@ class Document
32
32
  s.parent_section = sections_stack[-1].parent_section
33
33
  sections_stack[-1].parent_section.sections.append(s)
34
34
  sections_stack[-1] = s
35
+ # for search engine
36
+ s.heading.parent_heading = s.parent_section.heading
35
37
 
36
38
  elsif h.level > sections_stack[-1].heading.level
37
39
 
@@ -39,6 +41,8 @@ class Document
39
41
  s.parent_section = sections_stack[-1]
40
42
  sections_stack[-1].sections.append(s)
41
43
  sections_stack.push s
44
+ # for search engine
45
+ s.heading.parent_heading = s.parent_section.heading
42
46
 
43
47
  else
44
48
  sections_stack.pop while h.level < sections_stack[-1].heading.level
@@ -52,6 +56,8 @@ class Document
52
56
  sections_stack[-1].sections.append(s)
53
57
  end
54
58
  sections_stack[-1] = s
59
+ # for search engine
60
+ s.heading.parent_heading = s.parent_section.heading
55
61
  end
56
62
  end
57
63
  end
@@ -69,25 +69,6 @@ class Project # rubocop:disable Metrics/ClassLength,Style/Documentation
69
69
  create_search_data
70
70
  end
71
71
 
72
- def transform(file_extension)
73
- transform_all_specifications file_extension
74
- end
75
-
76
- def transform_all_specifications(_file_extension)
77
- path = @configuration.project_root_directory
78
-
79
- # find all specifications
80
- Dir.glob("#{path}/specifications/**/*.md").each do |f|
81
- puts f
82
- # make a copy with another extention to preserve the content
83
- f_directory = File.dirname(f)
84
- f_name = File.basename(f, File.extname(f)).downcase + '._md'
85
- FileUtils.copy_file(f, "#{f_directory}/#{f_name}")
86
- # transform the original one
87
- # but do nothing for now - TODO
88
- end
89
- end
90
-
91
72
  def parse_all_specifications
92
73
  path = @configuration.project_root_directory
93
74
  # do a lasy pass first to get the list of documents id
@@ -250,7 +231,7 @@ class Project # rubocop:disable Metrics/ClassLength,Style/Documentation
250
231
  @index = Index.new(@project)
251
232
  end
252
233
 
253
- def render_all_specifications(spec_list) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
234
+ def render_all_specifications(spec_list) # rubocop:disable Metrics/MethodLength
254
235
  path = @configuration.project_root_directory
255
236
 
256
237
  FileUtils.mkdir_p("#{path}/build/specifications")