aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorAlex Legler <alex@a3li.li>2016-07-20 14:37:49 +0200
committerAlex Legler <alex@a3li.li>2016-07-20 14:37:49 +0200
commitb145840323316610dd5a958cad89bbb84712ca5b (patch)
treeddfd7996d65feeccf927900131482842df3253f1 /app
downloadpackages-b145840323316610dd5a958cad89bbb84712ca5b.tar.gz
packages-b145840323316610dd5a958cad89bbb84712ca5b.tar.bz2
packages-b145840323316610dd5a958cad89bbb84712ca5b.zip
Initial commit w/currently running code
Diffstat (limited to 'app')
-rw-r--r--app/assets/images/.keep0
-rw-r--r--app/assets/javascripts/application.js18
-rw-r--r--app/assets/javascripts/arches.js2
-rw-r--r--app/assets/javascripts/index/typeahead.js26
-rw-r--r--app/assets/javascripts/kkuleomi.js9
-rw-r--r--app/assets/javascripts/packages/show.js24
-rw-r--r--app/assets/javascripts/useflags/render-bubbles.js70
-rw-r--r--app/assets/javascripts/useflags/typeahead.js25
-rw-r--r--app/assets/stylesheets/about.scss0
-rw-r--r--app/assets/stylesheets/application.css16
-rw-r--r--app/assets/stylesheets/arches.scss3
-rw-r--r--app/assets/stylesheets/categories.scss1
-rw-r--r--app/assets/stylesheets/index.scss9
-rw-r--r--app/assets/stylesheets/keywords.scss24
-rw-r--r--app/assets/stylesheets/misc.scss158
-rw-r--r--app/assets/stylesheets/packages.scss232
-rwxr-xr-xapp/assets/stylesheets/sprockets-octicons.scss217
-rw-r--r--app/assets/stylesheets/useflags.scss20
-rw-r--r--app/controllers/about_controller.rb27
-rw-r--r--app/controllers/application_controller.rb13
-rw-r--r--app/controllers/arches_controller.rb63
-rw-r--r--app/controllers/categories_controller.rb39
-rw-r--r--app/controllers/concerns/.keep0
-rw-r--r--app/controllers/concerns/package_update_feeds.rb35
-rw-r--r--app/controllers/index_controller.rb10
-rw-r--r--app/controllers/packages_controller.rb84
-rw-r--r--app/controllers/useflags_controller.rb50
-rw-r--r--app/helpers/application_helper.rb51
-rw-r--r--app/helpers/arches_helper.rb2
-rw-r--r--app/helpers/keywords_helper.rb90
-rw-r--r--app/helpers/links_helper.rb56
-rw-r--r--app/helpers/packages_helper.rb81
-rw-r--r--app/helpers/portage_helper.rb8
-rw-r--r--app/helpers/useflags_helper.rb5
-rw-r--r--app/jobs/category_update_job.rb39
-rw-r--r--app/jobs/masks_update_job.rb7
-rw-r--r--app/jobs/package_removal_job.rb16
-rw-r--r--app/jobs/package_update_job.rb13
-rw-r--r--app/jobs/record_change_job.rb30
-rw-r--r--app/jobs/useflags_update_job.rb78
-rw-r--r--app/mailers/.keep0
-rw-r--r--app/mailers/application_mailer.rb4
-rw-r--r--app/mailers/feedback_mailer.rb8
-rw-r--r--app/models/.keep0
-rw-r--r--app/models/category.rb39
-rw-r--r--app/models/change.rb13
-rw-r--r--app/models/concerns/.keep0
-rw-r--r--app/models/package.rb74
-rw-r--r--app/models/useflag.rb99
-rw-r--r--app/models/version.rb165
-rw-r--r--app/views/about/changelog.html.md9
-rw-r--r--app/views/about/feedback.html.erb64
-rw-r--r--app/views/about/feeds.html.erb23
-rw-r--r--app/views/about/help.html.erb11
-rw-r--r--app/views/about/index.html.erb23
-rw-r--r--app/views/about/legacy.atom.builder18
-rw-r--r--app/views/arches/index.html.erb36
-rw-r--r--app/views/arches/keyworded.html.erb23
-rw-r--r--app/views/arches/show.html.erb0
-rw-r--r--app/views/arches/stable.html.erb23
-rw-r--r--app/views/categories/_category_header.html.erb21
-rw-r--r--app/views/categories/_package_line.html.erb4
-rw-r--r--app/views/categories/index.html.erb52
-rw-r--r--app/views/categories/index.json.jbuilder5
-rw-r--r--app/views/categories/show.html.erb28
-rw-r--r--app/views/categories/show.json.jbuilder10
-rw-r--r--app/views/feedback_mailer/feedback_email.text.erb5
-rw-r--r--app/views/feeds/changes.atom.builder60
-rw-r--r--app/views/index/_package.html.erb8
-rw-r--r--app/views/index/index.html.erb53
-rw-r--r--app/views/layouts/application.html.erb153
-rw-r--r--app/views/layouts/mailer.html.erb5
-rw-r--r--app/views/layouts/mailer.text.erb1
-rw-r--r--app/views/packages/_changed_package.html.erb71
-rw-r--r--app/views/packages/_changelog.html.erb19
-rw-r--r--app/views/packages/_changelog_entry.html.erb31
-rw-r--r--app/views/packages/_herd.html.erb1
-rw-r--r--app/views/packages/_keyword_legend.html.erb17
-rw-r--r--app/views/packages/_maintainer.html.erb1
-rw-r--r--app/views/packages/_maintainer_needed_notice.html.erb7
-rw-r--r--app/views/packages/_maintainer_spacer.html.erb1
-rw-r--r--app/views/packages/_mask.html.erb30
-rw-r--r--app/views/packages/_masks.html.erb10
-rw-r--r--app/views/packages/_metadata.html.erb87
-rw-r--r--app/views/packages/_metadata_use.html.erb14
-rw-r--r--app/views/packages/_package_header.html.erb31
-rw-r--r--app/views/packages/_package_result_row.html.erb4
-rw-r--r--app/views/packages/_removal_notice.html.erb5
-rw-r--r--app/views/packages/_resources.html.erb35
-rw-r--r--app/views/packages/_useflag.html.erb5
-rw-r--r--app/views/packages/_version_card.html.erb14
-rw-r--r--app/views/packages/_version_row.html.erb12
-rw-r--r--app/views/packages/_versions.html.erb38
-rw-r--r--app/views/packages/added.html.erb23
-rw-r--r--app/views/packages/changelog.html.erb12
-rw-r--r--app/views/packages/changelog.json.jbuilder1
-rw-r--r--app/views/packages/keyworded.html.erb23
-rw-r--r--app/views/packages/resolve.json.jbuilder4
-rw-r--r--app/views/packages/search.html.erb38
-rw-r--r--app/views/packages/show.html.erb28
-rw-r--r--app/views/packages/show.json.jbuilder43
-rw-r--r--app/views/packages/stable.html.erb23
-rw-r--r--app/views/packages/suggest.json.jbuilder1
-rw-r--r--app/views/packages/updated.html.erb23
-rw-r--r--app/views/useflags/_useflag_header.html.erb5
-rw-r--r--app/views/useflags/_useflag_result_row.html.erb4
-rw-r--r--app/views/useflags/index.html.erb45
-rw-r--r--app/views/useflags/popular.json.jbuilder10
-rw-r--r--app/views/useflags/search.html.erb16
-rw-r--r--app/views/useflags/show.html.erb61
-rw-r--r--app/views/useflags/show_use_expand.html.erb57
-rw-r--r--app/views/useflags/suggest.json.jbuilder1
112 files changed, 3539 insertions, 0 deletions
diff --git a/app/assets/images/.keep b/app/assets/images/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/images/.keep
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
new file mode 100644
index 0000000..9bca50a
--- /dev/null
+++ b/app/assets/javascripts/application.js
@@ -0,0 +1,18 @@
+// This is a manifest file that'll be compiled into application.js, which will include all the files
+// listed below.
+//
+// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
+// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
+//
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// compiled file.
+//
+// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
+// about supported directives.
+//
+//= require jquery
+//= require jquery_ujs
+//= require turbolinks
+//= require jquery.typeahead.min
+//= require moment.min
+//= require_directory .
diff --git a/app/assets/javascripts/arches.js b/app/assets/javascripts/arches.js
new file mode 100644
index 0000000..dee720f
--- /dev/null
+++ b/app/assets/javascripts/arches.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/app/assets/javascripts/index/typeahead.js b/app/assets/javascripts/index/typeahead.js
new file mode 100644
index 0000000..3232fe8
--- /dev/null
+++ b/app/assets/javascripts/index/typeahead.js
@@ -0,0 +1,26 @@
+$(function() {
+ $('#q').typeahead({
+ order: 'asc',
+ dynamic: true,
+ delay: 500,
+ source: {
+ packages: {
+ display: 'name',
+ href: function(item) { return '/packages/' + item.category + '/' + item.name; },
+ url: [{
+ type: 'GET',
+ url: "/packages/suggest.json",
+ data: {
+ q: "{{query}}"
+ }
+ }, 'results'],
+ template: '<span class="kk-suggest-cat">{{category}}</span>/<span class="kk-suggest-pkg">{{name}}</span> <span class="kk-suggest-detail">{{description}}</span>'
+ }
+ },
+ callback: {
+ onClick: function(node, a, item, event) {
+ window.location = item.href;
+ }
+ }
+ });
+});
diff --git a/app/assets/javascripts/kkuleomi.js b/app/assets/javascripts/kkuleomi.js
new file mode 100644
index 0000000..3a01529
--- /dev/null
+++ b/app/assets/javascripts/kkuleomi.js
@@ -0,0 +1,9 @@
+$(document).on('ready page:load kkuleomi:ajax', function(event) {
+ $('[data-toggle="tooltip"]').tooltip();
+
+ $('.kk-i18n-date').each(function(idx) {
+ // TODO: Support different date formats
+ var me = $(this);
+ me.text(moment.unix(me.data('utcts')).local().format('ddd, D MMM YYYY HH:mm'));
+ });
+});
diff --git a/app/assets/javascripts/packages/show.js b/app/assets/javascripts/packages/show.js
new file mode 100644
index 0000000..01f5300
--- /dev/null
+++ b/app/assets/javascripts/packages/show.js
@@ -0,0 +1,24 @@
+$(function() {
+ var atom = $('#package-title').data('atom');
+
+ $.ajax({
+ url: '/packages/' + atom + '/changelog'
+ }).done(function(data) {
+ $('#changelog-container').html(data);
+ $(document).trigger('kkuleomi:ajax');
+ }).fail(function() {
+ $('#changelog-container > li').html('<span class="fa fa-fw fa-3x fa-ban text-danger"></span><br><br>Changelog currently not available. Please check back later.');
+ });
+
+ $('#kk-keyword-legend-btn').popover({
+ content: function(btn) {
+ return $('#kk-keyword-legend-text').html();
+ },
+ html: true,
+ trigger: 'toggle'
+ });
+
+ $('#kk-keyword-legend-btn').click(function() {
+ return false;
+ });
+});
diff --git a/app/assets/javascripts/useflags/render-bubbles.js b/app/assets/javascripts/useflags/render-bubbles.js
new file mode 100644
index 0000000..6c99e43
--- /dev/null
+++ b/app/assets/javascripts/useflags/render-bubbles.js
@@ -0,0 +1,70 @@
+$('#bubble-placeholder').show();
+
+var width = 600;
+ height = 600;
+
+var diameter = 960,
+ format = d3.format(",d"),
+ color = d3.scale.category20c();
+
+var bubble = d3.layout.pack()
+ .sort(null)
+ .size([width, height])
+ .padding(1.5);
+
+var svg = d3.select("#bubble-placeholder").append("svg")
+ .attr("width", width)
+ .attr("height", height)
+ .attr("class", "bubble");
+
+d3.json("/useflags/popular.json", function(error, root) {
+ if (error) throw error;
+
+ var node = svg.selectAll(".node")
+ .data(bubble.nodes(classes(root))
+ .filter(function(d) { return !d.children; }))
+ .enter().append("g")
+ .attr("class", "node")
+ .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
+
+ node.append("title")
+ .text(function(d) { return d.className + ": " + format(d.value); });
+
+ node.append("circle")
+ .attr("r", function(d) { return d.r; })
+ .attr("class", "kk-useflag-circle")
+ .attr("onclick", function(d) { return "location.href='/useflags/" + d.className + "';"; })
+ .style("fill", function(d) { return color(d.className); });
+
+ node.append("text")
+ .attr("dy", ".3em")
+ .attr('class', 'kk-useflag-circle')
+ .attr("onclick", function(d) { return "location.href='/useflags/" + d.className + "';"; })
+ .style("text-anchor", "middle")
+ .style("font-size", function(d) {
+ var len = d.className.substring(0, d.r / 3).length;
+ var size = d.r/3;
+ size *= 8 / len;
+ if (len == 1) {
+ size -= 60;
+ }
+ size += 1;
+ return Math.round(size)+'px';
+ })
+ .text(function(d) { return d.className.substring(0, d.r / 3); });
+});
+
+// Returns a flattened hierarchy containing all leaf nodes under the root.
+function classes(root) {
+ var classes = [];
+
+ function recurse(name, node) {
+ if (node.children) node.children.forEach(function(child) { recurse(node.name, child); });
+ else classes.push({packageName: name, className: node.name, value: node.size});
+ }
+
+ recurse(null, root);
+ return {children: classes};
+}
+
+d3.select(self.frameElement).style("height", height + "px");
diff --git a/app/assets/javascripts/useflags/typeahead.js b/app/assets/javascripts/useflags/typeahead.js
new file mode 100644
index 0000000..3912380
--- /dev/null
+++ b/app/assets/javascripts/useflags/typeahead.js
@@ -0,0 +1,25 @@
+$(function() {
+ $('#q').typeahead({
+ order: "asc",
+ dynamic: true,
+ source: {
+ use: {
+ display: 'name',
+ href: function(item) { return '/useflags/' + item.name; },
+ url: [{
+ type: 'GET',
+ url: "/useflags/suggest.json",
+ data: {
+ q: "{{query}}"
+ }
+ }, 'results'],
+ template: '<span>{{name}}</span> <span class="kk-suggest-detail">{{description}}</span>'
+ }
+ },
+ callback: {
+ onClick: function(node, a, item, event) {
+ window.location = item.href;
+ }
+ }
+ });
+});
diff --git a/app/assets/stylesheets/about.scss b/app/assets/stylesheets/about.scss
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/stylesheets/about.scss
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
new file mode 100644
index 0000000..6b38ea1
--- /dev/null
+++ b/app/assets/stylesheets/application.css
@@ -0,0 +1,16 @@
+/*
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
+ * listed below.
+ *
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
+ *
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
+ * compiled file so the styles you add here take precedence over styles defined in any styles
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
+ * file per style scope.
+ *
+ *= require_tree .
+ *= require jquery.typeahead.min
+ *= require_self
+ */
diff --git a/app/assets/stylesheets/arches.scss b/app/assets/stylesheets/arches.scss
new file mode 100644
index 0000000..f0daf89
--- /dev/null
+++ b/app/assets/stylesheets/arches.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Arches controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/categories.scss b/app/assets/stylesheets/categories.scss
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/app/assets/stylesheets/categories.scss
@@ -0,0 +1 @@
+
diff --git a/app/assets/stylesheets/index.scss b/app/assets/stylesheets/index.scss
new file mode 100644
index 0000000..00db929
--- /dev/null
+++ b/app/assets/stylesheets/index.scss
@@ -0,0 +1,9 @@
+.site-welcome {
+ font-size: 2.5em;
+ text-align: center;
+ margin-bottom: 1em;
+
+ @media screen and (max-width: 767px) {
+ font-size: 1.75em;
+ }
+}
diff --git a/app/assets/stylesheets/keywords.scss b/app/assets/stylesheets/keywords.scss
new file mode 100644
index 0000000..e860d2f
--- /dev/null
+++ b/app/assets/stylesheets/keywords.scss
@@ -0,0 +1,24 @@
+.kk-keyword-stable {
+ background-color: #81C784;
+ color: #2E7D32;
+}
+
+.kk-keyword-testing {
+ background-color: #FFF176;
+ color: #a08700;
+}
+
+.kk-keyword-unavailable {
+ background-color: #d5d5d5;
+ color: #666;
+}
+
+.kk-keyword-masked {
+ background-color: #EF9A9A;
+ color: #B71C1C;
+}
+
+.kk-keyword-unknown {
+ background-color: #fafafa;
+ color: #333;
+}
diff --git a/app/assets/stylesheets/misc.scss b/app/assets/stylesheets/misc.scss
new file mode 100644
index 0000000..f3c557d
--- /dev/null
+++ b/app/assets/stylesheets/misc.scss
@@ -0,0 +1,158 @@
+h1 {
+ a.kk-feed-icon {
+ font-size: 65%;
+ }
+}
+
+.label {
+ cursor: default;
+}
+
+.kk-cell-sep-right {
+ border-right-width: 3px !important;
+}
+
+.panel {
+ table.table {
+ tr {
+ th:first-child,
+ td:first-child {
+ padding-left: 1em;
+ }
+ }
+ }
+}
+
+.black {
+ color: #333;
+}
+
+td .alert {
+ margin-bottom: 0;
+}
+
+.kk-nobreak-cell {
+ white-space: nowrap;
+}
+
+.kk-panel-content-sorry {
+ background-color: #f0f0f0;
+ text-align: center;
+ padding-top: 2em;
+ padding-bottom: 2em;
+ color: #666;
+}
+
+.kk-3col-list {
+ columns: 3;
+ -webkit-columns: 3;
+ -moz-columns: 3;
+}
+
+.kk-4col-list {
+ columns: 4;
+ -webkit-columns: 4;
+ -moz-columns: 4;
+}
+
+.kk-5col-list {
+ columns: 5;
+ -webkit-columns: 5;
+ -moz-columns: 5;
+}
+
+.kk-6col-list {
+ columns: 6;
+ -webkit-columns: 6;
+ -moz-columns: 6;
+}
+
+.kk-col-list {
+ @media screen and (max-width: 767px) {
+ columns: 1;
+ -webkit-columns: 1;
+ -moz-columns: 1;
+ }
+
+ padding: 0;
+
+ li {
+ list-style-type: none;
+
+ a:link,
+ a:hover,
+ a:active,
+ a:visited {
+ display: block;
+ padding: .2em;
+ }
+
+ a:hover {
+ background-color: #eee;
+ border-radius: 2px;
+ text-decoration: none;
+ }
+ }
+}
+
+.kk-col-list-header {
+ margin-top: 1em;
+
+ .kk-group-header {
+ display: block;
+ border-bottom: 1px solid #eee;
+ }
+}
+
+.kk-col-list .kk-col-list-header:first-of-type {
+ margin-top: 0;
+}
+
+.kk-group-header {
+ color: #8a8a8a;
+ letter-spacing: 1px;
+ text-transform: uppercase;
+ font-size: 90%;
+}
+
+.kk-suggest-cat {
+ color: #8a8a8a;
+}
+
+.kk-suggest-pkg {
+}
+
+.kk-suggest-detail {
+ float: right;
+ color: #8a8a8a;
+ font-size: 90%;
+
+ @media screen and (max-width: 767px) {
+ display: block;
+ float: none;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+}
+
+body.kk .typeahead-list > li > a {
+ // Firefox workaround again
+ white-space: normal;
+}
+
+.kk-site-notice {
+ font-size: 90%;
+ padding-top: .5em;
+ padding-bottom: .5em;
+ text-align: center;
+}
+
+a.kk-box-meta-link:link,
+a.kk-box-meta-link:active,
+a.kk-box-meta-link:visited {
+ color: #aaa;
+}
+
+a.kk-box-meta-link:hover {
+ color: #555;
+}
diff --git a/app/assets/stylesheets/packages.scss b/app/assets/stylesheets/packages.scss
new file mode 100644
index 0000000..1c2811a
--- /dev/null
+++ b/app/assets/stylesheets/packages.scss
@@ -0,0 +1,232 @@
+.kk-keyword-header {
+ width: 5em;
+ font-size: 85%;
+ white-space: normal !important;
+}
+
+@media screen and (max-width: 767px) {
+ .kk-keyword-header {
+ min-width: 5em;
+ max-width: 5em;
+ }
+}
+
+.kk-version {
+ // Only set on small screens due to:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=488725
+ @media screen and (max-width: 767px) {
+ white-space: nowrap;
+ }
+}
+
+.kk-slot {
+ color: #888;
+}
+
+.kk-keyword {
+ text-align: center;
+ white-space: normal !important;
+}
+
+.kk-search-result-header {
+ margin-top: 0;
+}
+
+.kk-metadata-key {
+ font-weight: bold;
+}
+
+.kk-package-title .kk-package-icon {
+ float: left;
+}
+
+.kk-package-title .kk-package-name {
+ margin-left: 1.3em;
+ word-wrap: break-word;
+}
+
+.kk-package-title .kk-package-cat {
+ padding-left: 2.05em;
+}
+
+.kk-package-maindesc {
+ margin-top: 1.75em;
+}
+
+.kk-package-homepage {
+ margin: 0;
+ font-size: 125%;
+}
+
+.kk-byline {
+ padding-left: 1em;
+ font-style: italic;
+}
+
+.kk-commit {
+ font-family: monospace;
+}
+
+.kk-changelog-diffstat {
+ margin-top: 1em;
+ margin-bottom: 0;
+ border: 1px solid #ddd;
+ border-top: none;
+}
+
+.kk-changelog-diffstat {
+ a:link, a:visited, a:active {
+ color: #333;
+ }
+}
+
+.kk-changelog-diffstat-icon {
+ width: 20px;
+}
+
+.kk-octicon-spacer {
+ padding-left: 18px;
+}
+
+.kk-useflag a:link,
+.kk-useflag a:visited {
+ padding: 0.2em;
+ padding-top: 0.1em;
+ padding-bottom: 0.1em;
+ background-color: #f0f0f0;
+ border: 1px solid #eaeaea;
+ border-radius: 2px;
+ color: #333;
+ font-family: monospace;
+ font-size: 90%;
+}
+
+.kk-useflag a:hover {
+ background-color: #e9e9e9;
+ color: #305d8c;
+}
+
+.kk-useflag a:hover {
+ text-decoration: none;
+}
+
+.kk-useflag {
+ list-style-type: none;
+ margin-bottom: 0.4em;
+ margin-right: 0.2em;
+}
+
+.kk-useflag-container {
+ padding: 0;
+ margin-top: .4em;
+
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.kk-useflag-container-many {
+ justify-content: space-between;
+}
+
+.kk-useflag-container-few {
+ justify-content: flex-start;
+}
+
+.kk-useflag-group {
+ color: #8a8a8a;
+ letter-spacing: 1px;
+ text-transform: uppercase;
+ font-size: 90%;
+}
+
+.kk-versions-table {
+ .kk-restrict-label,
+ .kk-properties-label {
+ float: right;
+
+ @media screen and (max-width: 767px) {
+ float: none;
+ }
+ }
+}
+
+.kk-version-card > p:last-of-type {
+ margin-bottom: 0;
+}
+
+@media screen and (max-width: 767px) {
+ .kk-version-card > p:first-of-type {
+ margin-top: .5em;
+ }
+}
+
+.kk-mask {
+ background-color: #f2dede;
+}
+
+.kk-mask-details {
+ font-size: 90%;
+
+ .row {
+ margin-bottom: 1em;
+ }
+
+ margin-top: 1em;
+ margin-bottom: -1em;
+}
+
+.kk-mask-reason {
+ a:link,
+ a:visited {
+ color: inherit;
+ text-decoration: underline;
+ }
+}
+
+.kk-mask-atoms {
+ max-height: 3.6em;
+ overflow-x: scroll;
+ line-height: 1.2em;
+}
+
+.popover .kk-keyword-legend {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ width: 240px;
+}
+
+.kk-package-detailed {
+ h4 {
+ margin-bottom: 5px;
+ }
+}
+
+.kk-package-detailed-toolbox {
+ float:right;
+ margin-top: -1.75em;
+}
+
+.kk-inline-changelog-entry {
+ font-size: 90%;
+ border-radius: 2px;
+ border: 1px solid #ddd;
+ background-color: #f5f5f5;
+ margin-top: 5px;
+
+ a:link,
+ a:active,
+ a:visited,
+ a:hover {
+ display: block;
+ padding: 5px;
+ color: #333;
+ }
+
+ .kk-commit-message {
+ margin-left: .5em;
+ }
+
+ .kk-commit {
+ color: #999;
+ }
+}
diff --git a/app/assets/stylesheets/sprockets-octicons.scss b/app/assets/stylesheets/sprockets-octicons.scss
new file mode 100755
index 0000000..cef21ae
--- /dev/null
+++ b/app/assets/stylesheets/sprockets-octicons.scss
@@ -0,0 +1,217 @@
+@font-face {
+ font-family: 'octicons';
+ src: font-url('octicons.eot?#iefix') format('embedded-opentype'),
+ font-url('octicons.woff') format('woff'),
+ font-url('octicons.ttf') format('truetype'),
+ font-url('octicons.svg#octicons') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+
+// .octicon is optimized for 16px.
+// .mega-octicon is optimized for 32px but can be used larger.
+.octicon, .mega-octicon {
+ font: normal normal normal 16px/1 octicons;
+ display: inline-block;
+ text-decoration: none;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.mega-octicon { font-size: 32px; }
+
+.octicon-alert:before { content: '\f02d'} /*  */
+.octicon-arrow-down:before { content: '\f03f'} /*  */
+.octicon-arrow-left:before { content: '\f040'} /*  */
+.octicon-arrow-right:before { content: '\f03e'} /*  */
+.octicon-arrow-small-down:before { content: '\f0a0'} /*  */
+.octicon-arrow-small-left:before { content: '\f0a1'} /*  */
+.octicon-arrow-small-right:before { content: '\f071'} /*  */
+.octicon-arrow-small-up:before { content: '\f09f'} /*  */
+.octicon-arrow-up:before { content: '\f03d'} /*  */
+.octicon-microscope:before,
+.octicon-beaker:before { content: '\f0dd'} /*  */
+.octicon-bell:before { content: '\f0de'} /*  */
+.octicon-book:before { content: '\f007'} /*  */
+.octicon-bookmark:before { content: '\f07b'} /*  */
+.octicon-briefcase:before { content: '\f0d3'} /*  */
+.octicon-broadcast:before { content: '\f048'} /*  */
+.octicon-browser:before { content: '\f0c5'} /*  */
+.octicon-bug:before { content: '\f091'} /*  */
+.octicon-calendar:before { content: '\f068'} /*  */
+.octicon-check:before { content: '\f03a'} /*  */
+.octicon-checklist:before { content: '\f076'} /*  */
+.octicon-chevron-down:before { content: '\f0a3'} /*  */
+.octicon-chevron-left:before { content: '\f0a4'} /*  */
+.octicon-chevron-right:before { content: '\f078'} /*  */
+.octicon-chevron-up:before { content: '\f0a2'} /*  */
+.octicon-circle-slash:before { content: '\f084'} /*  */
+.octicon-circuit-board:before { content: '\f0d6'} /*  */
+.octicon-clippy:before { content: '\f035'} /*  */
+.octicon-clock:before { content: '\f046'} /*  */
+.octicon-cloud-download:before { content: '\f00b'} /*  */
+.octicon-cloud-upload:before { content: '\f00c'} /*  */
+.octicon-code:before { content: '\f05f'} /*  */
+.octicon-color-mode:before { content: '\f065'} /*  */
+.octicon-comment-add:before,
+.octicon-comment:before { content: '\f02b'} /*  */
+.octicon-comment-discussion:before { content: '\f04f'} /*  */
+.octicon-credit-card:before { content: '\f045'} /*  */
+.octicon-dash:before { content: '\f0ca'} /*  */
+.octicon-dashboard:before { content: '\f07d'} /*  */
+.octicon-database:before { content: '\f096'} /*  */
+.octicon-clone:before,
+.octicon-desktop-download:before { content: '\f0dc'} /*  */
+.octicon-device-camera:before { content: '\f056'} /*  */
+.octicon-device-camera-video:before { content: '\f057'} /*  */
+.octicon-device-desktop:before { content: '\f27c'} /*  */
+.octicon-device-mobile:before { content: '\f038'} /*  */
+.octicon-diff:before { content: '\f04d'} /*  */
+.octicon-diff-added:before { content: '\f06b'} /*  */
+.octicon-diff-ignored:before { content: '\f099'} /*  */
+.octicon-diff-modified:before { content: '\f06d'} /*  */
+.octicon-diff-removed:before { content: '\f06c'} /*  */
+.octicon-diff-renamed:before { content: '\f06e'} /*  */
+.octicon-ellipsis:before { content: '\f09a'} /*  */
+.octicon-eye-unwatch:before,
+.octicon-eye-watch:before,
+.octicon-eye:before { content: '\f04e'} /*  */
+.octicon-file-binary:before { content: '\f094'} /*  */
+.octicon-file-code:before { content: '\f010'} /*  */
+.octicon-file-directory:before { content: '\f016'} /*  */
+.octicon-file-media:before { content: '\f012'} /*  */
+.octicon-file-pdf:before { content: '\f014'} /*  */
+.octicon-file-submodule:before { content: '\f017'} /*  */
+.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */
+.octicon-file-symlink-file:before { content: '\f0b0'} /*  */
+.octicon-file-text:before { content: '\f011'} /*  */
+.octicon-file-zip:before { content: '\f013'} /*  */
+.octicon-flame:before { content: '\f0d2'} /*  */
+.octicon-fold:before { content: '\f0cc'} /*  */
+.octicon-gear:before { content: '\f02f'} /*  */
+.octicon-gift:before { content: '\f042'} /*  */
+.octicon-gist:before { content: '\f00e'} /*  */
+.octicon-gist-secret:before { content: '\f08c'} /*  */
+.octicon-git-branch-create:before,
+.octicon-git-branch-delete:before,
+.octicon-git-branch:before { content: '\f020'} /*  */
+.octicon-git-commit:before { content: '\f01f'} /*  */
+.octicon-git-compare:before { content: '\f0ac'} /*  */
+.octicon-git-merge:before { content: '\f023'} /*  */
+.octicon-git-pull-request-abandoned:before,
+.octicon-git-pull-request:before { content: '\f009'} /*  */
+.octicon-globe:before { content: '\f0b6'} /*  */
+.octicon-graph:before { content: '\f043'} /*  */
+.octicon-heart:before { content: '\2665'} /* ♥ */
+.octicon-history:before { content: '\f07e'} /*  */
+.octicon-home:before { content: '\f08d'} /*  */
+.octicon-horizontal-rule:before { content: '\f070'} /*  */
+.octicon-hubot:before { content: '\f09d'} /*  */
+.octicon-inbox:before { content: '\f0cf'} /*  */
+.octicon-info:before { content: '\f059'} /*  */
+.octicon-issue-closed:before { content: '\f028'} /*  */
+.octicon-issue-opened:before { content: '\f026'} /*  */
+.octicon-issue-reopened:before { content: '\f027'} /*  */
+.octicon-jersey:before { content: '\f019'} /*  */
+.octicon-key:before { content: '\f049'} /*  */
+.octicon-keyboard:before { content: '\f00d'} /*  */
+.octicon-law:before { content: '\f0d8'} /*  */
+.octicon-light-bulb:before { content: '\f000'} /*  */
+.octicon-link:before { content: '\f05c'} /*  */
+.octicon-link-external:before { content: '\f07f'} /*  */
+.octicon-list-ordered:before { content: '\f062'} /*  */
+.octicon-list-unordered:before { content: '\f061'} /*  */
+.octicon-location:before { content: '\f060'} /*  */
+.octicon-gist-private:before,
+.octicon-mirror-private:before,
+.octicon-git-fork-private:before,
+.octicon-lock:before { content: '\f06a'} /*  */
+.octicon-logo-github:before { content: '\f092'} /*  */
+.octicon-mail:before { content: '\f03b'} /*  */
+.octicon-mail-read:before { content: '\f03c'} /*  */
+.octicon-mail-reply:before { content: '\f051'} /*  */
+.octicon-mark-github:before { content: '\f00a'} /*  */
+.octicon-markdown:before { content: '\f0c9'} /*  */
+.octicon-megaphone:before { content: '\f077'} /*  */
+.octicon-mention:before { content: '\f0be'} /*  */
+.octicon-milestone:before { content: '\f075'} /*  */
+.octicon-mirror-public:before,
+.octicon-mirror:before { content: '\f024'} /*  */
+.octicon-mortar-board:before { content: '\f0d7'} /*  */
+.octicon-mute:before { content: '\f080'} /*  */
+.octicon-no-newline:before { content: '\f09c'} /*  */
+.octicon-octoface:before { content: '\f008'} /*  */
+.octicon-organization:before { content: '\f037'} /*  */
+.octicon-package:before { content: '\f0c4'} /*  */
+.octicon-paintcan:before { content: '\f0d1'} /*  */
+.octicon-pencil:before { content: '\f058'} /*  */
+.octicon-person-add:before,
+.octicon-person-follow:before,
+.octicon-person:before { content: '\f018'} /*  */
+.octicon-pin:before { content: '\f041'} /*  */
+.octicon-plug:before { content: '\f0d4'} /*  */
+.octicon-repo-create:before,
+.octicon-gist-new:before,
+.octicon-file-directory-create:before,
+.octicon-file-add:before,
+.octicon-plus:before { content: '\f05d'} /*  */
+.octicon-primitive-dot:before { content: '\f052'} /*  */
+.octicon-primitive-square:before { content: '\f053'} /*  */
+.octicon-pulse:before { content: '\f085'} /*  */
+.octicon-question:before { content: '\f02c'} /*  */
+.octicon-quote:before { content: '\f063'} /*  */
+.octicon-radio-tower:before { content: '\f030'} /*  */
+.octicon-repo-delete:before,
+.octicon-repo:before { content: '\f001'} /*  */
+.octicon-repo-clone:before { content: '\f04c'} /*  */
+.octicon-repo-force-push:before { content: '\f04a'} /*  */
+.octicon-gist-fork:before,
+.octicon-repo-forked:before { content: '\f002'} /*  */
+.octicon-repo-pull:before { content: '\f006'} /*  */
+.octicon-repo-push:before { content: '\f005'} /*  */
+.octicon-rocket:before { content: '\f033'} /*  */
+.octicon-rss:before { content: '\f034'} /*  */
+.octicon-ruby:before { content: '\f047'} /*  */
+.octicon-screen-full:before { content: '\f066'} /*  */
+.octicon-screen-normal:before { content: '\f067'} /*  */
+.octicon-search-save:before,
+.octicon-search:before { content: '\f02e'} /*  */
+.octicon-server:before { content: '\f097'} /*  */
+.octicon-settings:before { content: '\f07c'} /*  */
+.octicon-shield:before { content: '\f0e1'} /*  */
+.octicon-log-in:before,
+.octicon-sign-in:before { content: '\f036'} /*  */
+.octicon-log-out:before,
+.octicon-sign-out:before { content: '\f032'} /*  */
+.octicon-squirrel:before { content: '\f0b2'} /*  */
+.octicon-star-add:before,
+.octicon-star-delete:before,
+.octicon-star:before { content: '\f02a'} /*  */
+.octicon-stop:before { content: '\f08f'} /*  */
+.octicon-repo-sync:before,
+.octicon-sync:before { content: '\f087'} /*  */
+.octicon-tag-remove:before,
+.octicon-tag-add:before,
+.octicon-tag:before { content: '\f015'} /*  */
+.octicon-telescope:before { content: '\f088'} /*  */
+.octicon-terminal:before { content: '\f0c8'} /*  */
+.octicon-three-bars:before { content: '\f05e'} /*  */
+.octicon-thumbsdown:before { content: '\f0db'} /*  */
+.octicon-thumbsup:before { content: '\f0da'} /*  */
+.octicon-tools:before { content: '\f031'} /*  */
+.octicon-trashcan:before { content: '\f0d0'} /*  */
+.octicon-triangle-down:before { content: '\f05b'} /*  */
+.octicon-triangle-left:before { content: '\f044'} /*  */
+.octicon-triangle-right:before { content: '\f05a'} /*  */
+.octicon-triangle-up:before { content: '\f0aa'} /*  */
+.octicon-unfold:before { content: '\f039'} /*  */
+.octicon-unmute:before { content: '\f0ba'} /*  */
+.octicon-versions:before { content: '\f064'} /*  */
+.octicon-watch:before { content: '\f0e0'} /*  */
+.octicon-remove-close:before,
+.octicon-x:before { content: '\f081'} /*  */
+.octicon-zap:before { content: '\26A1'} /* ⚡ */
diff --git a/app/assets/stylesheets/useflags.scss b/app/assets/stylesheets/useflags.scss
new file mode 100644
index 0000000..90f667d
--- /dev/null
+++ b/app/assets/stylesheets/useflags.scss
@@ -0,0 +1,20 @@
+.kk-useflag-circle {
+ cursor: pointer;
+}
+
+.kk-useflag-bubble-container {
+ text-align: center;
+ overflow: scroll;
+}
+
+.kk-useflag-listing {
+ a:link,
+ a:visited {
+ color: #333;
+ }
+}
+
+form.useflag-search {
+ margin-top: 2.5em;
+ margin-bottom: 1em;
+}
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb
new file mode 100644
index 0000000..e41c3b5
--- /dev/null
+++ b/app/controllers/about_controller.rb
@@ -0,0 +1,27 @@
+class AboutController < ApplicationController
+ before_action :set_nav
+
+ def feedback
+ if params.key? :feedback
+ FeedbackMailer.feedback_email(params[:feedback], params[:contact]).deliver_now
+ render text: 'Thank you for your feedback!', layout: 'application'
+ end
+ end
+
+ def index
+ end
+
+ def feeds
+ end
+
+ def legacy
+ @feed_type = 'legacy'
+ @feed_title = 'packages.gentoo.org Legacy Feed'
+ end
+
+ private
+
+ def set_nav
+ @nav = :about
+ end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
new file mode 100644
index 0000000..9e8e1ca
--- /dev/null
+++ b/app/controllers/application_controller.rb
@@ -0,0 +1,13 @@
+class ApplicationController < ActionController::Base
+ before_action :set_locale, :set_caching
+
+ def set_locale
+ I18n.locale = params[:hl] || I18n.default_locale
+ rescue
+ I18n.default_locale
+ end
+
+ def set_caching
+ expires_in 10.minutes, public: true
+ end
+end
diff --git a/app/controllers/arches_controller.rb b/app/controllers/arches_controller.rb
new file mode 100644
index 0000000..cbbcb65
--- /dev/null
+++ b/app/controllers/arches_controller.rb
@@ -0,0 +1,63 @@
+class ArchesController < ApplicationController
+ before_action :set_nav
+ before_action :set_arch, only: [:show, :added, :updated, :stable, :keyworded]
+
+ def index
+ end
+
+ def show
+ end
+
+ def stable
+ @changes = stabled_packages @arch
+ render_changes_feed :stable, t(:feed_stable_arch, arch: @arch)
+ end
+
+ def keyworded
+ @changes = keyworded_packages @arch
+ render_changes_feed :keyworded, t(:feed_keyworded, arch: @arch)
+ end
+
+ private
+
+ def set_nav
+ @nav = :arches
+ end
+
+ def set_arch
+ fail ActionController::RoutingError, 'No such architecture' unless ::KKULEOMI_ARCHES.include? params[:id]
+ @arch = params[:id]
+ @feed_id = @arch
+ end
+
+ def render_changes_feed(type, title)
+ respond_to do |wants|
+ wants.html {}
+ wants.atom do
+ @feed_type = type
+ @feed_title = title
+ render template: 'feeds/changes'
+ end
+ end
+ end
+
+ def keyworded_packages(arch)
+ Rails.cache.fetch("keyworded_packages/#{arch}", expires_in: 10.minutes) do
+ Change.filter_all({ change_type: 'keyword', arches: arch },
+ size: 50,
+ sort: { created_at: { order: 'desc' } }).map do |change|
+ change.to_os(:change_type, :package, :category, :version, :arches, :created_at)
+ end
+ end
+ end
+
+ def stabled_packages(arch)
+ Rails.cache.fetch("stabled_packages/#{arch}", expires_in: 10.minutes) do
+ Change.filter_all({ change_type: 'stable', arches: arch },
+ size: 50,
+ sort: { created_at: { order: 'desc' } }).map do |change|
+ change.to_os(:change_type, :package, :category, :version, :arches, :created_at)
+ end
+ end
+ end
+end
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
new file mode 100644
index 0000000..e144df7
--- /dev/null
+++ b/app/controllers/categories_controller.rb
@@ -0,0 +1,39 @@
+class CategoriesController < ApplicationController
+ before_action :set_category, only: [:show, :search]
+ before_action :set_nav
+
+ def index
+ @categories = Category.all_sorted_by(:name, :asc)
+ end
+
+ def show
+ @packages = Rails.cache.fetch("category/#{@category.name}/packages",
+ expires_in: 10.minutes) do
+ Package.find_all_by(:category,
+ @category.name,
+ sort: { name_sort: { order: 'asc' } }).map do |pkg|
+ pkg.to_os(:name, :atom, :description)
+ end
+ end
+
+ @description = t(:desc_categories_show,
+ category: @category.name,
+ description: @category.description)
+ end
+
+ def search
+ end
+
+ private
+
+ def set_category
+ @category = Category.find_by(:name, params[:id])
+ fail ActionController::RoutingError, 'No such category' unless @category
+
+ @title = @category.name
+ end
+
+ def set_nav
+ @nav = :packages
+ end
+end
diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/controllers/concerns/.keep
diff --git a/app/controllers/concerns/package_update_feeds.rb b/app/controllers/concerns/package_update_feeds.rb
new file mode 100644
index 0000000..2d20672
--- /dev/null
+++ b/app/controllers/concerns/package_update_feeds.rb
@@ -0,0 +1,35 @@
+module PackageUpdateFeeds
+ extend ActiveSupport::Concern
+
+ def new_packages
+ Rails.cache.fetch('new_packages', expires_in: 10.minutes) do
+ Change.find_all_by(:change_type, 'new_package', { size: 50, sort: { created_at: { order: 'desc' } } }).map do |change|
+ change.to_os(:change_type, :package, :category, :created_at)
+ end
+ end
+ end
+
+ def version_bumps
+ Rails.cache.fetch('version_bumps', expires_in: 10.minutes) do
+ Change.find_all_by(:change_type, 'version_bump', { size: 50, sort: { created_at: { order: 'desc' } } }).map do |change|
+ change.to_os(:change_type, :package, :category, :version, :created_at)
+ end
+ end
+ end
+
+ def keyworded_packages
+ Rails.cache.fetch('keyworded_packages', expires_in: 10.minutes) do
+ Change.find_all_by(:change_type, 'keyword', { size: 50, sort: { created_at: { order: 'desc' } } }).map do |change|
+ change.to_os(:change_type, :package, :category, :version, :arches, :created_at)
+ end
+ end
+ end
+
+ def stabled_packages
+ Rails.cache.fetch('stabled_packages', expires_in: 10.minutes) do
+ Change.find_all_by(:change_type, 'stable', { size: 50, sort: { created_at: { order: 'desc' } } }).map do |change|
+ change.to_os(:change_type, :package, :category, :version, :arches, :created_at)
+ end
+ end
+ end
+end
diff --git a/app/controllers/index_controller.rb b/app/controllers/index_controller.rb
new file mode 100644
index 0000000..ba866c6
--- /dev/null
+++ b/app/controllers/index_controller.rb
@@ -0,0 +1,10 @@
+class IndexController < ApplicationController
+ include PackageUpdateFeeds
+
+ def index
+ @nav = :index
+
+ @new_packages = new_packages[0..9]
+ @version_bumps = version_bumps[0..9]
+ end
+end
diff --git a/app/controllers/packages_controller.rb b/app/controllers/packages_controller.rb
new file mode 100644
index 0000000..ee098ae
--- /dev/null
+++ b/app/controllers/packages_controller.rb
@@ -0,0 +1,84 @@
+class PackagesController < ApplicationController
+ include PackageUpdateFeeds
+ before_action :set_nav
+
+ def index
+ redirect_to categories_path
+ end
+
+ def search
+ @offset = params[:o].to_i || 0
+ @packages = Package.default_search(params[:q], @offset)
+
+ redirect_to package_path(@packages.first).gsub('%2F', '/') if @packages.size == 1
+ end
+
+ def suggest
+ @packages = Package.suggest(params[:q])
+ end
+
+ def resolve
+ @packages = Package.resolve(params[:atom])
+ end
+
+ def show
+ @package = Package.find_by(:atom, params[:id])
+ fail ActionController::RoutingError, 'No such package' unless @package
+
+ # Enable this in 2024 (when we have full-color emojis on a Linux desktop)
+ # @title = ' &#x1F4E6; %s' % @package.atom
+ @title = @package.atom
+ @description = 'Gentoo package %s: %s' % [@package.atom, @package.description]
+ end
+
+ def changelog
+ @package = Package.find_by(:atom, params[:id])
+ fail ActionController::RoutingError, 'No such package' unless @package
+
+ @changelog = Rails.cache.fetch("changelog/#{@package.atom}", expires_in: 10.minutes) do
+ Portage::Util::History.for(@package.category, @package.name, 5)
+ end
+
+ respond_to do |wants|
+ wants.html { render layout: false }
+ wants.json {}
+ end
+ end
+
+ def added
+ @changes = new_packages
+ render_changes_feed :added, t(:feed_added)
+ end
+
+ def updated
+ @changes = version_bumps
+ render_changes_feed :updated, t(:feed_updated)
+ end
+
+ def stable
+ @changes = stabled_packages
+ render_changes_feed :stable, t(:feed_stable)
+ end
+
+ def keyworded
+ @changes = keyworded_packages
+ render_changes_feed :keyworded, t(:feed_keyworded)
+ end
+
+ private
+
+ def render_changes_feed(type, title)
+ respond_to do |wants|
+ wants.html {}
+ wants.atom do
+ @feed_type = type
+ @feed_title = title
+ render template: 'feeds/changes'
+ end
+ end
+ end
+
+ def set_nav
+ @nav = :packages
+ end
+end
diff --git a/app/controllers/useflags_controller.rb b/app/controllers/useflags_controller.rb
new file mode 100644
index 0000000..0fa74f4
--- /dev/null
+++ b/app/controllers/useflags_controller.rb
@@ -0,0 +1,50 @@
+class UseflagsController < ApplicationController
+ before_action :set_nav
+
+ def index
+ @title = t :use_flags
+ end
+
+ def show
+ @useflags = Useflag.get_flags(params[:id])
+
+ if @useflags.empty? || (@useflags[:use_expand].empty? && @useflags[:local].empty? && @useflags[:global].empty?)
+ fail ActionController::RoutingError, 'No such useflag'
+ end
+
+ @packages = Package.find_atoms_by_useflag(params[:id])
+ @title = '%s – %s' % [params[:id], t(:use_flags)]
+
+ unless @useflags[:use_expand].empty?
+ @useflag = @useflags[:use_expand].first
+ @use_expand_flags = Useflag.find_all_by(:use_expand_prefix, @useflag.use_expand_prefix)
+ @use_expand_flag_name = @useflag.use_expand_prefix.upcase
+
+ render template: 'useflags/show_use_expand'
+ return
+ else
+ render template: 'useflags/show'
+ end
+ end
+
+ def search
+ # TODO: Different search?
+ @flags = Useflag.suggest(params[:q])
+ end
+
+ def suggest
+ @flags = Useflag.suggest(params[:q])
+ end
+
+ def popular
+ @popular_useflags = Rails.cache.fetch('popular_useflags', expires_in: 24.hours) do
+ Version.get_popular_useflags(100)
+ end
+ end
+
+ private
+
+ def set_nav
+ @nav = :use
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
new file mode 100644
index 0000000..619582c
--- /dev/null
+++ b/app/helpers/application_helper.rb
@@ -0,0 +1,51 @@
+module ApplicationHelper
+ def cp_to_atom(category, package)
+ '%s/%s' % [category, package]
+ end
+
+ def atom_add_version(atom, version)
+ '%s-%s' % [atom, version]
+ end
+
+ # Generates a somewhat sensible atom ID
+ def atom_id(*args)
+ ['tag:packages.gentoo.org,2015-10-03', args].flatten.compact.join ':'
+ end
+
+ def alternate_feed_link(url, description, mime = 'application/atom+xml')
+ tag :link,
+ rel: 'alternate',
+ href: url,
+ title: description,
+ type: mime
+ end
+
+ # Renders a label displaying the first letters of the components of a string
+ def abbreviated_label(items, css_class, message_id)
+ return '' if items.nil? || items.empty?
+
+ letters = strip_conditionals(items).map { |r| r[0].upcase }.uniq
+
+ content_tag :span,
+ letters.join(', '),
+ class: 'label %s' % css_class,
+ title: t(message_id, list: items.join(' '))
+ end
+
+ def last_import_start
+ Rails.cache.fetch(::KK_CACHE_LAST_IMPORT)
+ end
+
+ def i18n_date(date, format = '%a, %e %b %Y %H:%M')
+ content_tag :span,
+ l(date, format: format),
+ class: 'kk-i18n-date',
+ :'data-utcts' => date.strftime('%s'),
+ :'data-format' => format.to_s,
+ title: date.to_formatted_s(:rfc822)
+ end
+
+ def kk_changelog
+ File.read('CHANGES.md')
+ end
+end
diff --git a/app/helpers/arches_helper.rb b/app/helpers/arches_helper.rb
new file mode 100644
index 0000000..670d125
--- /dev/null
+++ b/app/helpers/arches_helper.rb
@@ -0,0 +1,2 @@
+module ArchesHelper
+end
diff --git a/app/helpers/keywords_helper.rb b/app/helpers/keywords_helper.rb
new file mode 100644
index 0000000..2444a6b
--- /dev/null
+++ b/app/helpers/keywords_helper.rb
@@ -0,0 +1,90 @@
+# Helper methods for dealing with package version KEYWORDS
+module KeywordsHelper
+ # Renders an icon for a keyword status
+ def keyword_icon_tag(keyword)
+ css_class = KK_KEYWORD_ICON[keyword]
+
+ if css_class
+ content_tag :span,
+ '',
+ class: ['octicon', css_class]
+ else
+ ''
+ end
+ end
+
+ # Retrieves the CSS class for a keyword
+ def keyword_class(keyword)
+ KK_KEYWORD_CLASS[keyword] || nil
+ end
+
+ # Displays a keyword icon plus text-mdoe browser fallback
+ def keyword_icon(keyword, arch)
+ capture do
+ concat keyword_icon_tag(keyword)
+ concat keyword_fallback_tag(keyword, arch)
+ end
+ end
+
+ # Renders a keyword as a familiar string
+ def keyword_string(keyword, arch)
+ case keyword
+ when :stable
+ arch
+ when :testing
+ '~%s' % arch
+ when :unavailable
+ '-%s' % arch
+ when :masked
+ '[M]%s' % arch
+ else
+ '?%s' % arch
+ end
+ end
+
+ def keyword_fallback_tag(keyword, arch)
+ content_tag :span,
+ keyword_string(keyword, arch),
+ class: 'sr-only'
+ end
+
+ def verbalize_version_visibility(version, arch)
+ keyword = t(KK_KEYWORD_VERBALIZATION[version.keyword(arch)])
+
+ keyword_str = keyword
+ keyword_str = '%s (%s)' % [
+ t(KK_KEYWORD_VERBALIZATION[:masked]),
+ keyword
+ ] if version.is_masked?
+
+ t 'keyword_tooltip',
+ version: version.version,
+ keyword: keyword_str,
+ arch: arch
+ end
+
+ def keyword_cell(version, arch, large_separator = false)
+ effective_keyword = version.effective_keyword arch
+
+ css_class = ['kk-keyword']
+ css_class << 'kk-cell-sep-right' if large_separator
+ css_class << keyword_class(effective_keyword)
+
+ content_tag :td,
+ keyword_icon(effective_keyword, arch),
+ class: css_class,
+ title: verbalize_version_visibility(version, arch)
+ end
+
+ def keyword_label(version, arch)
+ effective_keyword = version.effective_keyword arch
+
+ css_class = ['label']
+ css_class << keyword_class(effective_keyword)
+
+ content_tag :span,
+ keyword_string(effective_keyword, arch),
+ class: css_class,
+ title: verbalize_version_visibility(version, arch)
+ end
+end
diff --git a/app/helpers/links_helper.rb b/app/helpers/links_helper.rb
new file mode 100644
index 0000000..5a28fb0
--- /dev/null
+++ b/app/helpers/links_helper.rb
@@ -0,0 +1,56 @@
+module LinksHelper
+ # Slash-in-Link-Fix
+ # Replaces the URLencoded slash with a proper slash
+ def slf(input)
+ input.gsub('%2F', '/')
+ end
+
+ def link_to_gitweb_commit(commitid)
+ link_to commitid[0...8],
+ gitweb_commit_url(commitid),
+ title: commitid,
+ class: 'kk-commit'
+ end
+
+ def gitweb_commit_url(commitid)
+ 'https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=%s' % commitid
+ end
+
+ def link_to_gitweb_ebuild_diff(name, commitid, cat, pkg)
+ link_to name, 'https://gitweb.gentoo.org/repo/gentoo.git/diff/%s/%s/%s?id=%s' % [cat, pkg, name, commitid]
+ end
+
+ def link_to_license_text(license)
+ link_to license, 'https://gitweb.gentoo.org/repo/gentoo.git/plain/licenses/%s' % license
+ end
+
+ def link_to_category(category)
+ link_to category.name,
+ category_path(category),
+ title: category.description,
+ 'data-toggle' => 'tooltip',
+ 'data-placement' => 'right'
+ end
+
+ def link_to_package(atom)
+ link_to atom, slf(package_path(atom))
+ end
+
+ def link_to_herd(herd)
+ link_to herd, 'https://www.gentoo.org/inside-gentoo/developers/herds.html#%s' % herd
+ end
+
+ def link_to_bug(str, bugid)
+ link_to str, 'https://bugs.gentoo.org/show_bug.cgi?id=%s' % bugid
+ end
+
+ def absolute_link_to_package(atom)
+ slf package_url(atom)
+ end
+
+ def feed_icon(url)
+ content_tag :a,
+ content_tag(:span, '', class: 'fa fa-fw fa-rss-square'),
+ title: t(:atom_feed), href: url, class: 'kk-feed-icon'
+ end
+end
diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb
new file mode 100644
index 0000000..ed90f5d
--- /dev/null
+++ b/app/helpers/packages_helper.rb
@@ -0,0 +1,81 @@
+# Helpers for displaying package models
+module PackagesHelper
+ def restrict_label(version)
+ abbreviated_label version.restrict,
+ 'label-danger kk-restrict-label',
+ :restrict_tooltip
+ end
+
+ def properties_label(version)
+ abbreviated_label version.properties,
+ 'label-info kk-properties-label',
+ :properties_tooltip
+ end
+
+ def version_labels(version)
+ capture do
+ concat restrict_label(version)
+ concat properties_label(version)
+ end
+ end
+
+ def annotate_license_str(str)
+ str.split(/\s/).map do |license|
+ if license[0] =~ /[[:alpha:]]/ && !license.end_with?('?')
+ link_to_license_text license
+ else
+ h license
+ end
+ end.join(' ').html_safe
+ end
+
+ def annotate_bugs(str)
+ annotated_str = str.gsub(/([bB]ug\s+|[bB]ug\s+#|#)(\d+)/) do
+ link_to_bug("#{$1}#{$2}", $2)
+ end
+
+ sanitize(annotated_str, tags: ['a'], attributes: ['href'])
+ end
+
+ # Filters duplicate masks
+ def filter_masks(versions)
+ masks = {}
+
+ versions.each do |version|
+ version.masks.each do |mask|
+ masks[mask['reason']] = mask
+ end
+ end
+
+ masks.values
+ end
+
+ def version_slot(slot, subslot = nil)
+ title = "subslot #{subslot}" if subslot && !subslot.empty?
+
+ content_tag :span,
+ sanitize('&#x2008;:&#x2008;%s' % slot),
+ class: 'kk-slot',
+ title: title
+ end
+
+ # Returns a list of members belonging to a project
+ def project_members(project)
+ Portage::Util::Projects.cached_instance.inherited_members(project)
+ end
+
+ # Tries to find a matching changelog entry for a change object
+ def matching_changelog_entry(change)
+ changelog = Rails.cache.fetch("changelog/#{cp_to_atom(change.category, change.package)}", expires_in: 10.minutes) do
+ Portage::Util::History.for(change.category, change.package, 5)
+ end
+
+ changelog.each do |changelog_entry|
+ if changelog_entry[:files][:added].include?('%s-%s.ebuild' % [change.package, change.version])
+ return changelog_entry
+ end
+ end
+
+ nil
+ end
+end
diff --git a/app/helpers/portage_helper.rb b/app/helpers/portage_helper.rb
new file mode 100644
index 0000000..3e1b9e6
--- /dev/null
+++ b/app/helpers/portage_helper.rb
@@ -0,0 +1,8 @@
+module PortageHelper
+ # Strips condition constructs ("foo? ()") from a Portage definition
+ def strip_conditionals(ary)
+ ary.reject do |item|
+ (not item[0] =~ /[[:alpha:]]/) or item.end_with? '?'
+ end
+ end
+end
diff --git a/app/helpers/useflags_helper.rb b/app/helpers/useflags_helper.rb
new file mode 100644
index 0000000..258cec2
--- /dev/null
+++ b/app/helpers/useflags_helper.rb
@@ -0,0 +1,5 @@
+module UseflagsHelper
+ def annotate_useflag_description(str)
+ sanitize(str.gsub(/<pkg>([^<]+)<\/pkg>/) { pkg=$~[1] ; link_to(pkg, slf(package_path(pkg))) }, tags: ['a'], attributes: ['href'])
+ end
+end
diff --git a/app/jobs/category_update_job.rb b/app/jobs/category_update_job.rb
new file mode 100644
index 0000000..6418bb4
--- /dev/null
+++ b/app/jobs/category_update_job.rb
@@ -0,0 +1,39 @@
+class CategoryUpdateJob < ActiveJob::Base
+ queue_as :default
+
+ def perform(*args)
+ category_path, options = args
+
+ model = Portage::Repository::Category.new(category_path)
+ category = Category.find_by(:name, model.name) || Category.new
+ idx_packages = Package.find_all_by(:category, model.name) || []
+
+ if category.needs_import? model
+ category.import! model
+ end
+
+ idx_package_map = Hash[idx_packages.map { |p| [p.name, p] }]
+ model_package_map = Hash[model.packages.map { |p| [p.name, p] }]
+
+ idx_keys = idx_package_map.keys
+ model_keys = model_package_map.keys
+
+ new_p = model_keys - idx_keys
+ eql_p = model_keys & idx_keys
+ del_p = idx_keys - model_keys
+
+ new_p.each do |pkg_name|
+ PackageUpdateJob.perform_later model_package_map[pkg_name].path, options.merge(package_state: 'new')
+ end
+
+ eql_p.each do |pkg_name|
+ if idx_package_map[pkg_name].needs_import? model_package_map[pkg_name]
+ PackageUpdateJob.perform_later model_package_map[pkg_name].path, options.merge(package_state: 'existing')
+ end
+ end
+
+ del_p.each do |pkg_name|
+ PackageRemovalJob.perform_later '%s/%s' % [category.name, pkg_name]
+ end
+ end
+end
diff --git a/app/jobs/masks_update_job.rb b/app/jobs/masks_update_job.rb
new file mode 100644
index 0000000..a6574a0
--- /dev/null
+++ b/app/jobs/masks_update_job.rb
@@ -0,0 +1,7 @@
+class MasksUpdateJob < ActiveJob::Base
+ queue_as :default
+
+ def perform(*_args)
+ Portage::Util::Masks.update!
+ end
+end
diff --git a/app/jobs/package_removal_job.rb b/app/jobs/package_removal_job.rb
new file mode 100644
index 0000000..1041f05
--- /dev/null
+++ b/app/jobs/package_removal_job.rb
@@ -0,0 +1,16 @@
+class PackageRemovalJob < ActiveJob::Base
+ queue_as :default
+
+ def perform(*args)
+ atom, _options = args
+
+ package_doc = Package.find_by(:atom, atom)
+ return if package_doc.nil?
+
+ package_doc.versions.each(&:delete)
+ package_doc.delete
+
+ Rails.logger.warn { "Package deleted: #{atom}" }
+ # USE flags are cleaned up by the UseflagsUpdateJob
+ end
+end
diff --git a/app/jobs/package_update_job.rb b/app/jobs/package_update_job.rb
new file mode 100644
index 0000000..8c4fd43
--- /dev/null
+++ b/app/jobs/package_update_job.rb
@@ -0,0 +1,13 @@
+class PackageUpdateJob < ActiveJob::Base
+ queue_as :default
+
+ def perform(*args)
+ path, options = args
+ package_model = Portage::Repository::Package.new(path)
+ package_doc = Package.find_by(:atom, package_model.to_cp) || Package.new
+
+ if package_doc.needs_import? package_model
+ package_doc.import!(package_model, options)
+ end
+ end
+end
diff --git a/app/jobs/record_change_job.rb b/app/jobs/record_change_job.rb
new file mode 100644
index 0000000..b2b4b10
--- /dev/null
+++ b/app/jobs/record_change_job.rb
@@ -0,0 +1,30 @@
+class RecordChangeJob < ActiveJob::Base
+ queue_as :default
+
+ # Creates a Change object for the given data
+ def perform(args)
+ c = Change.new
+ c.package = args[:package]
+ c.category = args[:category]
+ c.actor = args[:actor] if args.has_key? :actor
+
+ if args[:type] == 'new_package'
+ c.change_type = 'new_package'
+ elsif args[:type] == 'version_bump'
+ c.change_type = 'version_bump'
+ c.version = args[:version]
+ elsif args[:type] == 'stable'
+ c.change_type = 'stable'
+ c.version = args[:version]
+ c.arches = args[:arches]
+ elsif args[:type] == 'keyword'
+ c.change_type = 'keyword'
+ c.version = args[:version]
+ c.arches = args[:arches]
+ elsif args[:type] == 'package_removed'
+ c.change_type = 'removal'
+ end
+
+ c.save
+ end
+end
diff --git a/app/jobs/useflags_update_job.rb b/app/jobs/useflags_update_job.rb
new file mode 100644
index 0000000..d6f9e9b
--- /dev/null
+++ b/app/jobs/useflags_update_job.rb
@@ -0,0 +1,78 @@
+class UseflagsUpdateJob < ActiveJob::Base
+ queue_as :default
+
+ def perform(*args)
+ repo = Portage::Repository::Model.new KKULEOMI_PORTDIR
+
+ update_global repo
+ update_use_expand repo
+ end
+
+ def update_global(repo)
+ model_flags = repo.global_useflags
+ index_flags = Useflag.global
+
+ new_flags = model_flags.keys - index_flags.keys
+ del_flags = index_flags.keys - model_flags.keys
+ eql_flags = model_flags.keys & index_flags.keys
+
+ new_flags.each do |flag|
+ flag_doc = Useflag.new
+ flag_doc.name = flag
+ flag_doc.description = model_flags[flag]
+ flag_doc.scope = 'global'
+ flag_doc.save
+ end
+
+ eql_flags.each do |flag|
+ unless index_flags[flag].description == model_flags[flag]
+ index_flags[flag].description = model_flags[flag]
+ index_flags[flag].save
+ end
+ end
+
+ del_flags.each do |flag|
+ index_flags[flag].delete
+ end
+ end
+
+ def update_use_expand(repo)
+ model_flags = repo.use_expand_flags
+ index_flags = Useflag.use_expand
+
+ # Calculate keys only once
+ index_flag_keys = index_flags.keys
+
+ # Record processed flags to find deletion candidates
+ flag_status = Hash[index_flag_keys.map {|key| [key, false] }]
+
+ model_flags.each_pair do |variable, values_hsh|
+ values_hsh.each_pair do |flag, desc|
+ _flag = '%s_%s' % [variable, flag]
+ flag_status[_flag] = true
+
+ # Already present ones
+ if index_flag_keys.include? _flag
+ unless index_flags[_flag].description == desc
+ index_flags[_flag].description = desc
+ index_flags[_flag].save
+ end
+ else
+ # New flag
+ flag_doc = Useflag.new
+ flag_doc.name = _flag
+ flag_doc.description = desc
+ flag_doc.scope = 'use_expand'
+ flag_doc.use_expand_prefix = variable
+ flag_doc.save
+ end
+ end
+ end
+
+ # Find and process removed flags
+ flag_status.each_pair do |flag, status|
+ index_flags[flag].delete unless status
+ end
+ end
+
+end
diff --git a/app/mailers/.keep b/app/mailers/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/mailers/.keep
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
new file mode 100644
index 0000000..28eebce
--- /dev/null
+++ b/app/mailers/application_mailer.rb
@@ -0,0 +1,4 @@
+class ApplicationMailer < ActionMailer::Base
+ default from: 'gpackages@gentoo.org'
+ layout 'mailer'
+end
diff --git a/app/mailers/feedback_mailer.rb b/app/mailers/feedback_mailer.rb
new file mode 100644
index 0000000..3ec045c
--- /dev/null
+++ b/app/mailers/feedback_mailer.rb
@@ -0,0 +1,8 @@
+class FeedbackMailer < ApplicationMailer
+ def feedback_email(feedback, contact)
+ @feedback = feedback
+ @contact = contact
+
+ mail(to: KKULEOMI_FEEDBACK_RECIPIENT, subject: 'Feedback')
+ end
+end
diff --git a/app/models/.keep b/app/models/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/models/.keep
diff --git a/app/models/category.rb b/app/models/category.rb
new file mode 100644
index 0000000..5fa31ea
--- /dev/null
+++ b/app/models/category.rb
@@ -0,0 +1,39 @@
+class Category
+ include Elasticsearch::Persistence::Model
+ include Kkuleomi::Store::Model
+
+ index_name "packages-#{Rails.env}"
+
+ attribute :name, String, mapping: { index: 'not_analyzed' }
+ attribute :description, String
+ attribute :metadata_hash, String, mapping: { index: 'not_analyzed' }
+
+ # Determines if the document model needs an update from the repository model
+ #
+ # @param [Portage::Repository::Category] category_model
+ def needs_import?(category_model)
+ metadata_hash != category_model.metadata_hash
+ end
+
+ # Populates values from a repository category model
+ #
+ # @param [Portage::Repository::Category] category_model Input category model
+ def import(category_model)
+ self.name = category_model.name
+ self.description = category_model.description
+ self.metadata_hash = category_model.metadata_hash
+ end
+
+ # Populates values from a repository category model and saves
+ #
+ # @param [Portage::Repository::Category] category_model Input category model
+ def import!(category_model)
+ import(category_model)
+ save
+ end
+
+ # Returns the URL parameter for referencing this package (Rails internal stuff)
+ def to_param
+ name
+ end
+end
diff --git a/app/models/change.rb b/app/models/change.rb
new file mode 100644
index 0000000..9ffe258
--- /dev/null
+++ b/app/models/change.rb
@@ -0,0 +1,13 @@
+class Change
+ include Elasticsearch::Persistence::Model
+ include Kkuleomi::Store::Model
+
+ index_name "packages-#{Rails.env}"
+
+ attribute :package, String, mapping: { index: 'not_analyzed' }
+ attribute :category, String, mapping: { index: 'not_analyzed' }
+ attribute :change_type, String, mapping: { index: 'not_analyzed' }
+ attribute :version, String, mapping: { index: 'not_analyzed' }
+ attribute :arches, String, mapping: { index: 'not_analyzed' }
+ attribute :commit, Hash, default: {}, mapping: { type: 'object' }
+end
diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/models/concerns/.keep
diff --git a/app/models/package.rb b/app/models/package.rb
new file mode 100644
index 0000000..24cbd8b
--- /dev/null
+++ b/app/models/package.rb
@@ -0,0 +1,74 @@
+class Package
+ include Elasticsearch::Persistence::Model
+ include Kkuleomi::Store::Model
+ include Kkuleomi::Store::Models::PackageImport
+ include Kkuleomi::Store::Models::PackageSearch
+
+ index_name "packages-#{Rails.env}"
+
+ attribute :category, String, mapping: { index: 'not_analyzed' }
+ attribute :name, String, mapping: { index: 'not_analyzed' }
+ attribute :name_sort, String, mapping: { index: 'not_analyzed' }
+ attribute :atom, String, mapping: { index: 'not_analyzed' }
+ attribute :description, String
+ attribute :longdescription, String
+ attribute :homepage, String, default: [], mapping: { index: 'not_analyzed' }
+ attribute :license, String, mapping: { index: 'not_analyzed' }
+ attribute :licenses, String, default: [], mapping: { index: 'not_analyzed' }
+ attribute :herds, String, default: [], mapping: { index: 'not_analyzed' }
+ attribute :maintainers, Array, default: [], mapping: { type: 'object' }
+ attribute :useflags, Hash, default: {}, mapping: { type: 'object' }
+ attribute :metadata_hash, String, mapping: { index: 'not_analyzed' }
+
+ def category_model
+ @category_model ||= Category.find_by(:name, category)
+ end
+
+ def to_param
+ atom
+ end
+
+ # Are all of the versions of this package pending for removal?
+ #
+ # @return [Boolean] true, if all of the versions' masks look like a removal mask
+ def removal_pending?
+ versions.map(&:removal_pending?).uniq == [true]
+ end
+
+ def has_useflags?
+ useflags && !(useflags['local'].empty? && useflags['global'].empty? && useflags['use_expand'])
+ end
+
+ def versions
+ @versions ||= Version.find_all_by_parent(self, sort: { sort_key: { order: 'asc' } })
+ end
+
+ def latest_version
+ versions.first
+ end
+
+ def version(version_str)
+ versions.each { |version| return version if version.version == version_str }
+
+ nil
+ end
+
+ # Does this package need a maintainer?
+ #
+ # @return [Boolean] true, if it is assigned to maintainer-needed or has no maintainers
+ def needs_maintainer?
+ (maintainers.size == 1 && maintainers.first['email'] == 'maintainer-needed@gentoo.org') ||
+ maintainers.empty? && herds.empty?
+ end
+
+ private
+
+ # Splits a license string into single licenses, stripping the permitted logic constructs
+ def split_license_str(str)
+ return [] unless str
+
+ str.split(/\s/).reject do |license|
+ (not license[0] =~ /[[:alpha:]]/) or license.end_with? '?'
+ end
+ end
+end
diff --git a/app/models/useflag.rb b/app/models/useflag.rb
new file mode 100644
index 0000000..1139c97
--- /dev/null
+++ b/app/models/useflag.rb
@@ -0,0 +1,99 @@
+class Useflag
+ include Elasticsearch::Persistence::Model
+ include Kkuleomi::Store::Model
+
+ index_name "packages-#{Rails.env}"
+
+ attribute :name, String, mapping: { index: 'not_analyzed' }
+ attribute :description, String
+ attribute :atom, String, mapping: { index: 'not_analyzed' }
+ attribute :scope, String, mapping: { index: 'not_analyzed' }
+ attribute :use_expand_prefix, String, mapping: { index: 'not_analyzed' }
+
+ def all_fields
+ [:name, :description, :atom, :scope, :use_expand_prefix]
+ end
+
+ def to_param
+ name
+ end
+
+ def strip_use_expand
+ name.gsub(use_expand_prefix + '_', '')
+ end
+
+ class << self
+ # Retrieves all flags sorted by their state
+ def get_flags(name)
+ result = { local: {}, global: [], use_expand: [] }
+
+ find_all_by(:name, name).each do |flag|
+ case flag.scope
+ when 'local'
+ result[:local][flag.atom] = flag
+ when 'global'
+ result[:global] << flag
+ when 'use_expand'
+ result[:use_expand] << flag
+ end
+ end
+
+ result
+ end
+
+ def suggest(q)
+ results = Useflag.search(
+ size: 20,
+ query: { match_phrase_prefix: { name: q } }
+ )
+
+ processed_results = {}
+ results.each do |result|
+ if processed_results.key? result.name
+ processed_results[result.name] = {
+ name: result.name,
+ description: '(multiple definitions)',
+ scope: 'multi'
+ }
+ else
+ processed_results[result.name] = result
+ end
+ end
+
+ processed_results.values.sort { |a, b| a[:name].length <=> b[:name].length }
+ end
+
+ # Loads the local USE flags for a given package in a name -> model hash
+ #
+ # @param [String] atom Package to find flags for
+ # @return [Hash]
+ def local_for(atom)
+ map_by_name find_all_by(:atom, atom)
+ end
+
+ # Maps the global USE flags in the index by their name
+ # This is expensive!
+ #
+ def global
+ map_by_name find_all_by(:scope, 'global')
+ end
+
+ # Maps the USE_EXPAND variables in the index by their name
+ #
+ def use_expand
+ map_by_name find_all_by(:scope, 'use_expand')
+ end
+
+ private
+
+ def map_by_name(collection)
+ map = {}
+
+ collection.each do |item|
+ map[item.name] = item
+ end
+
+ map
+ end
+ end
+end
diff --git a/app/models/version.rb b/app/models/version.rb
new file mode 100644
index 0000000..1dc28f8
--- /dev/null
+++ b/app/models/version.rb
@@ -0,0 +1,165 @@
+class Version
+ include Elasticsearch::Persistence::Model
+ include Kkuleomi::Store::Model
+ include Kkuleomi::Store::Models::VersionImport
+
+ index_name "packages-#{Rails.env}"
+
+ attribute :version, String, mapping: { index: 'not_analyzed' }
+ attribute :package, String, mapping: { index: 'not_analyzed' }
+ attribute :atom, String, mapping: { index: 'not_analyzed' }
+ attribute :sort_key, Integer, mapping: { index: 'not_analyzed' }
+ attribute :slot, String, mapping: { index: 'not_analyzed' }
+ attribute :subslot, String, mapping: { index: 'not_analyzed' }
+ attribute :eapi, String, mapping: { index: 'not_analyzed' }
+ attribute :keywords, String, mapping: { index: 'not_analyzed' }
+ attribute :masks, Array, default: [], mapping: { type: 'object' }
+ attribute :use, String, default: [], mapping: { index: 'not_analyzed' }
+ attribute :restrict, String, default: [], mapping: { index: 'not_analyzed' }
+ attribute :properties, String, default: [], mapping: { index: 'not_analyzed' }
+ attribute :metadata_hash, String, mapping: { index: 'not_analyzed' }
+
+ # Returns the keywording state on a given architecture
+ #
+ # @param [String] arch Architecture to query
+ # @return [Symbol] :stable, :testing, :unavailable, :unknown
+ def keyword(arch)
+ @keyword_info_cache ||= parse_keywords keywords
+
+ if @keyword_info_cache[:arches].key? arch
+ @keyword_info_cache[:arches][arch]
+ else
+ if @keyword_info_cache[:exclude_all]
+ :unavailable
+ else
+ :unknown
+ end
+ end
+ end
+
+ # Returns the effective keyword on a given architecture, accounting for masks
+ #
+ # @param [String] arch Architecture to query
+ # @return [Symbol] Keyword status
+ def effective_keyword(arch)
+ if is_masked?(arch)
+ :masked
+ else
+ keyword(arch)
+ end
+ end
+
+ # Returns the masks that apply to the given architecture
+ #
+ def mask(arch)
+ masks.reject do |m|
+ if m['arch'] == '*'
+ false
+ else
+ m['arch'] != arch
+ end
+ end
+ end
+
+ # Checks the masks whether one sounds like a package removal.
+ def removal_pending?
+ return false if masks.empty?
+
+ masks.each do |m|
+ if m['reason'].include?('removal') || m['reason'].include?('Removal')
+ return true
+ end
+ end
+
+ false
+ end
+
+ def is_masked?(arch = nil)
+ !mask(arch).empty?
+ end
+
+ # Returns supported USE flags categorized by local, global, and USE_EXPAND
+ # Typically called in the import phase, not live
+ #
+ # @return [Hash]
+ def useflags
+ @useflags ||= calc_useflags
+ end
+
+ # Retrieves the most widely used USE flags by all versions
+ # Note that packages with many versions are over-represented
+ def self.get_popular_useflags(n = 50)
+ search(
+ query: { match_all: {} },
+ aggs: {
+ group_by_flag: {
+ terms: {
+ field: 'use',
+ size: n
+ }
+ }
+ },
+ size: 0
+ ).response.aggregations['group_by_flag'].buckets
+ end
+
+ # Parses a keyword array and assigns tags for each arch
+ #
+ # @param [Array<String>] keywords Input keywords
+ # @return [Hash] Parsed keywords
+ def parse_keywords(keywords)
+ res = { exclude_all: false, arches: {} }
+ return res unless keywords
+
+ keywords.each do |kw|
+ if kw == '-*'
+ res[:exclude_all] = true
+ next
+ end
+
+ if kw.start_with? '-'
+ res[:arches][kw[1..-1]] = :unavailable
+ next
+ end
+
+ if kw.start_with? '~'
+ res[:arches][kw[1..-1]] = :testing
+ next
+ end
+
+ res[:arches][kw] = :stable
+ end
+
+ res
+ end
+
+ private
+
+ def calc_useflags
+ result = { local: {}, global: {}, use_expand: {} }
+
+ local_flag_map = Useflag.local_for(atom.gsub("-#{version}", ''))
+ local_flags = local_flag_map.keys
+
+ use.sort.each do |flag|
+ if local_flags.include? flag
+ result[:local][flag] = local_flag_map[flag].to_hsh
+ else
+ useflag = Useflag.find_by(:name, flag)
+
+ # This should not happen, but let's be sure
+ next unless useflag
+
+ if useflag.scope == 'global'
+ result[:global][useflag.name] = useflag.to_hsh
+ elsif useflag.scope == 'use_expand'
+ prefix = useflag.use_expand_prefix.upcase
+ result[:use_expand][prefix] ||= {}
+ result[:use_expand][prefix][useflag.name.gsub(useflag.use_expand_prefix + '_', '')] = useflag.to_hsh
+ end
+ end
+ end
+
+ result
+ end
+end
diff --git a/app/views/about/changelog.html.md b/app/views/about/changelog.html.md
new file mode 100644
index 0000000..070d979
--- /dev/null
+++ b/app/views/about/changelog.html.md
@@ -0,0 +1,9 @@
+<ol class="breadcrumb">
+ <li><a href="/"><%= t :home %></a></li>
+ <li><a href="/about"><%= t :about %></a></li>
+ <li class="active"><%= t :changelog %></li>
+</ol>
+
+# <%= t :changelog %>
+
+<%= kk_changelog %>
diff --git a/app/views/about/feedback.html.erb b/app/views/about/feedback.html.erb
new file mode 100644
index 0000000..4a1f1f3
--- /dev/null
+++ b/app/views/about/feedback.html.erb
@@ -0,0 +1,64 @@
+<ol class="breadcrumb">
+ <li><a href="/"><%= t :home %></a></li>
+ <li><a href="/about"><%= t :about %></a></li>
+ <li class="active"><%= t :feedback %></li>
+</ol>
+
+<h1>Feedback</h1>
+
+<p class="lead">
+ Thanks for checking out the new packages.gentoo.org!
+</p>
+<p>
+ This site is currently in an <abbr title="minimum viable product">MVP</abbr> state and will be extended further to provide more useful features.
+ To help us prioritize new features and learn about your use case for the site, please share your ideas below.
+ <br><br>
+</p>
+
+<div class="row">
+ <div class="col-md-8">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">Send Feedback</h3>
+ </div>
+ <div class="panel-body">
+ <form class="form-horizontal" method="post" action="/about/feedback">
+ <div class="form-group">
+ <label for="feedback" class="col-sm-2 control-label">Your Feedback:</label>
+ <div class="col-sm-10">
+ <textarea name="feedback" id="feedback" class="form-control" rows="10" placeholder="Please be sure to explain issues in detail and with exact URL references."></textarea>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="contact" class="col-sm-2 control-label">Contact (optional):</label>
+ <div class="col-sm-10">
+ <input type="text" name="contact" class="form-control" id="contact" placeholder="How can we reach you to follow up on your feedback?">
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="submit" class="btn btn-default">Send</button>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-4">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">Other ways to get in touch</h3>
+ </div>
+ <div class="list-group">
+ <a href="mailto:gpackages@gentoo.org" class="list-group-item">
+ <span class="fa fa-fw fa-envelope"></span>
+ E-Mail: gpackages@gentoo.org
+ </a>
+ <a href="irc://irc.gentoo.org/gentoo-www" class="list-group-item">
+ <span class="fa fa-fw fa-comments-o"></span>
+ IRC: #gentoo-www
+ </a>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/app/views/about/feeds.html.erb b/app/views/about/feeds.html.erb
new file mode 100644
index 0000000..5a9c1cd
--- /dev/null
+++ b/app/views/about/feeds.html.erb
@@ -0,0 +1,23 @@
+<ol class="breadcrumb">
+ <li><a href="/"><%= t :home %></a></li>
+ <li><a href="/about"><%= t :about %></a></li>
+ <li class="active"><%= t :update_feeds %></li>
+</ol>
+
+<h1><%= t :update_feeds %></h1>
+
+<p>
+ You can find Atom feeds here:
+</p>
+
+<ul>
+ <li>
+ For all packages: Right column on the <a href="/categories">category listing</a>.
+ </li>
+ <li>
+ For specific architectures: In the <a href="/arches">architectures section</a>.
+ </li>
+ <li>
+ For specific packages: In the <em>Resources</em> box on the respective package pages.
+ </li>
+</ul>
diff --git a/app/views/about/help.html.erb b/app/views/about/help.html.erb
new file mode 100644
index 0000000..77ddf5b
--- /dev/null
+++ b/app/views/about/help.html.erb
@@ -0,0 +1,11 @@
+<ol class="breadcrumb">
+ <li><a href="/"><%= t :home %></a></li>
+ <li><a href="/about"><%= t :about %></a></li>
+ <li class="active"><%= t :help %></li>
+</ol>
+
+<h1><%= t :help %></h1>
+
+<h2 id="keyword-legend"><%= t :keyword_table_legend %></h2>
+
+<%= render partial: 'packages/keyword_legend' %>
diff --git a/app/views/about/index.html.erb b/app/views/about/index.html.erb
new file mode 100644
index 0000000..5d9ed0f
--- /dev/null
+++ b/app/views/about/index.html.erb
@@ -0,0 +1,23 @@
+<ol class="breadcrumb">
+ <li><a href="/"><%= t :home %></a></li>
+ <li class="active"><%= t :about %></li>
+</ol>
+
+<h1>About packages.gentoo.org</h1>
+
+<p>Welcome to the new packages.gentoo.org!</p>
+
+<p>
+ This section will be extended with further information as the site continues to develop.
+ Feel free to <a href="/about/feedback">get in touch</a> if you have any questions that are not answered on this page.
+</p>
+
+<h2>FAQ</h2>
+
+<dl>
+ <dt>How often is the site updated?</dt>
+ <dd>
+ Updates are scheduled <strong>every 10 minutes</strong> and are processed using delayed jobs.
+ You can find the last time an import task was started in the footer.
+ </dd>
+</dl>
diff --git a/app/views/about/legacy.atom.builder b/app/views/about/legacy.atom.builder
new file mode 100644
index 0000000..91c0928
--- /dev/null
+++ b/app/views/about/legacy.atom.builder
@@ -0,0 +1,18 @@
+atom_feed(id: atom_id(@feed_type, 'feed')) do |feed|
+ feed.title @feed_title
+ feed.updated Time.now
+
+ feed.entry('', id: atom_id(@feed_type, 'deprecated'), url: about_feeds_url) do |entry|
+ entry.title 'This feed is deprecated'
+ entry.content <<END_CONTENT.strip
+This is a legacy feed from the previous version of packages.gentoo.org
+
+With our recent site relaunch, the feed setup has changed as well.
+To continue receiving updates about Gentoo packages, please visit the Feeds section of our new packages website at:
+
+ https://packages.gentoo.org/about/feeds
+
+Thank you for your interest in our packages site.
+END_CONTENT
+ end
+end
diff --git a/app/views/arches/index.html.erb b/app/views/arches/index.html.erb
new file mode 100644
index 0000000..8684eb1
--- /dev/null
+++ b/app/views/arches/index.html.erb
@@ -0,0 +1,36 @@
+<ol class="breadcrumb">
+ <li><a href="/">Home</a></li>
+ <li class="active"><%= t :architectures %></li>
+</ol>
+
+<h1><%= t :architectures %></h1>
+
+<p><%= t :arches_intro %></p>
+
+<div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><%= t :architectures %></h3>
+ </div>
+ <div class="table-responsive">
+ <table class="table table-striped">
+ <col>
+ <col style="width: 20em;">
+ <col style="width: 20em;">
+ <tbody>
+ <% ::KKULEOMI_ARCHES.sort.each do |arch| %>
+ <tr>
+ <th class="kk-nobreak-cell"><%= arch %></th>
+ <td>
+ <%= link_to t(:keyworded_packages), keyworded_arch_path(id: arch) %>
+ <%= feed_icon keyworded_arch_path(id: arch, format: :atom) %>
+ </td>
+ <td>
+ <%= link_to t(:stable_packages), stable_arch_path(id: arch) %>
+ <%= feed_icon stable_arch_path(id: arch, format: :atom) %>
+ </td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/app/views/arches/keyworded.html.erb b/app/views/arches/keyworded.html.erb
new file mode 100644
index 0000000..b7ae03d
--- /dev/null
+++ b/app/views/arches/keyworded.html.erb
@@ -0,0 +1,23 @@
+<ol class="breadcrumb">
+ <li><a href="/">Home</a></li>
+ <li><%= link_to t(:architectures), arches_path %></li>
+ <li class="active"><%= t :keyworded_packages %></li>
+</ol>
+
+<h1>
+ <%= t :keyworded_packages %> (<%= @arch %>)
+ <%= feed_icon keyworded_arch_path(id: @arch, format: :atom) %>
+</h1>
+
+<% cache("keyworded-full-#{@arch}-#{@changes.hash}") do %>
+ <ul class="list-group">
+ <% @changes.each do |change|
+ _package = Package.find_by(:atom, cp_to_atom(change.category, change.package)) %>
+ <%= render partial: 'packages/changed_package', object: change, as: 'change', locals: { package: _package, version: _package.version(change.version) } %>
+ <% end %>
+ </ul>
+<% end %>
+
+<% content_for :head do %>
+ <%= alternate_feed_link(keyworded_arch_url(id: @arch, format: :atom), t(:atom_feed)) %>
+<% end %>
diff --git a/app/views/arches/show.html.erb b/app/views/arches/show.html.erb
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/views/arches/show.html.erb
diff --git a/app/views/arches/stable.html.erb b/app/views/arches/stable.html.erb
new file mode 100644
index 0000000..b1a4548
--- /dev/null
+++ b/app/views/arches/stable.html.erb
@@ -0,0 +1,23 @@
+<ol class="breadcrumb">
+ <li><a href="/">Home</a></li>
+ <li><%= link_to t(:architectures), arches_path %></li>
+ <li class="active"><%= t :stable_packages %></li>
+</ol>
+
+<h1>
+ <%= t :stable_packages %> (<%= @arch %>)
+ <%= feed_icon stable_arch_path(id: @arch, format: :atom) %>
+</h1>
+
+<% cache("stable-full-#{@arch}-#{@changes.hash}") do %>
+ <ul class="list-group">
+ <% @changes.each do |change|
+ _package = Package.find_by(:atom, cp_to_atom(change.category, change.package)) %>
+ <%= render partial: 'packages/changed_package', object: change, as: 'change', locals: { package: _package, version: _package.version(change.version) } %>
+ <% end %>
+ </ul>
+<% end %>
+
+<% content_for :head do %>
+ <%= alternate_feed_link(stable_arch_url(id: @arch, format: :atom), t(:atom_feed)) %>
+<% end %>
diff --git a/app/views/categories/_category_header.html.erb b/app/views/categories/_category_header.html.erb
new file mode 100644
index 0000000..b52cecf
--- /dev/null
+++ b/app/views/categories/_category_header.html.erb
@@ -0,0 +1,21 @@
+<ol class="breadcrumb">
+ <li><a href="/">Home</a></li>
+ <li><%= link_to t(:packages), categories_path %></li>
+ <li class="active"><%= category.name %></li>
+</ol>
+
+<div class="row">
+ <div class="col-md-4">
+ <h1 class="stick-top">
+ <span class="fa fa-fw fa-cubes"></span>
+ <%= category.name %>
+ </h1>
+ </div>
+ <div class="col-md-8">
+ <p class="lead" style="margin: 0;">
+ <%= category.description %>
+ </p>
+ </div>
+</div>
+
+<hr> \ No newline at end of file
diff --git a/app/views/categories/_package_line.html.erb b/app/views/categories/_package_line.html.erb
new file mode 100644
index 0000000..7ac4a43
--- /dev/null
+++ b/app/views/categories/_package_line.html.erb
@@ -0,0 +1,4 @@
+<tr>
+ <th class="kk-nobreak-cell"><%= link_to package.name, slf(package_path(package.atom)) %></th>
+ <td><%= package.description %></td>
+</tr>
diff --git a/app/views/categories/index.html.erb b/app/views/categories/index.html.erb
new file mode 100644
index 0000000..9c19c9c
--- /dev/null
+++ b/app/views/categories/index.html.erb
@@ -0,0 +1,52 @@
+<ol class="breadcrumb">
+ <li><a href="/">Home</a></li>
+ <li class="active"><%= t :packages %></li>
+</ol>
+
+<h1><%= t :packages %></h1>
+
+<div class="row">
+ <div class="col-md-9">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><%= t :browse_categories %></h3>
+ </div>
+ <div class="panel-body">
+ <ul class="kk-col-list kk-4col-list kk-category-listing">
+ <%- prev_letter = 'z' -%>
+ <% @categories.each do |category| %>
+ <%- unless category.name[0].upcase == prev_letter ; prev_letter = category.name[0].upcase -%>
+ <li class="kk-col-list-header"><span class="kk-group-header"><%= prev_letter %></span></li>
+ <%- end -%>
+ <li><%= link_to_category category %></li>
+ <% end %>
+ </ul>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-3">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><%= t :update_feeds %></h3>
+ </div>
+ <div class="list-group">
+ <a href="<%= added_packages_path %>" class="list-group-item">
+ <span class="fa fa-fw fa-history"></span>
+ <%= t :added_packages %>
+ </a>
+ <a href="<%= updated_packages_path %>" class="list-group-item">
+ <span class="fa fa-fw fa-asterisk"></span>
+ <%= t :updated_packages %>
+ </a>
+ <a href="<%= stable_packages_path %>" class="list-group-item">
+ <span class="fa fa-fw fa-check-circle-o"></span>
+ <%= t :stable_packages %>
+ </a>
+ <a href="<%= keyworded_packages_path %>" class="list-group-item">
+ <span class="fa fa-fw fa-circle-o"></span>
+ <%= t :keyworded_packages %>
+ </a>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/app/views/categories/index.json.jbuilder b/app/views/categories/index.json.jbuilder
new file mode 100644
index 0000000..12cc02e
--- /dev/null
+++ b/app/views/categories/index.json.jbuilder
@@ -0,0 +1,5 @@
+json.array!(@categories) do |category|
+ json.extract! category, :name
+ json.extract! category, :description
+ json.url category_url(category, format: :json)
+end
diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb
new file mode 100644
index 0000000..41cceef
--- /dev/null
+++ b/app/views/categories/show.html.erb
@@ -0,0 +1,28 @@
+<%= render partial: 'category_header', object: @category, as: 'category' %>
+
+<div class="row">
+ <div class="col-md-9">
+ <!--<p>
+ <input type="text" class="form-control form-control-xl" placeholder="Search packages in <%= @category.name %>">
+ </p>-->
+
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">All packages</h3>
+ </div>
+ <table class="table">
+ <%= render partial: 'package_line', collection: @packages, as: 'package' %>
+ </table>
+ </div>
+ </div>
+ <div class="col-md-3">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">Statistics</h3>
+ </div>
+ <div class="panel-body">
+ <%= @packages.count %> <%= t :packages %>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/app/views/categories/show.json.jbuilder b/app/views/categories/show.json.jbuilder
new file mode 100644
index 0000000..fcf3811
--- /dev/null
+++ b/app/views/categories/show.json.jbuilder
@@ -0,0 +1,10 @@
+json.extract! @category, :name
+json.href slf category_url(id: @category.name)
+
+json.packages @packages do |package|
+ json.name package.name
+ json.description package.description
+ json.href slf(package_url(id: package.atom))
+end
+
+json.extract! @category, :updated_at \ No newline at end of file
diff --git a/app/views/feedback_mailer/feedback_email.text.erb b/app/views/feedback_mailer/feedback_email.text.erb
new file mode 100644
index 0000000..40b0b98
--- /dev/null
+++ b/app/views/feedback_mailer/feedback_email.text.erb
@@ -0,0 +1,5 @@
+Feedback:
+-------------------------------------------------------------------------------
+<%= @feedback %>
+-------------------------------------------------------------------------------
+Contact: <%= @contact %>
diff --git a/app/views/feeds/changes.atom.builder b/app/views/feeds/changes.atom.builder
new file mode 100644
index 0000000..5991f45
--- /dev/null
+++ b/app/views/feeds/changes.atom.builder
@@ -0,0 +1,60 @@
+@feed_id ||= nil
+
+atom_feed(id: atom_id(@feed_type, @feed_id, 'feed')) do |feed|
+ feed.title @feed_title
+ feed.updated !@changes.empty? ? @changes.first.created_at : Time.now
+
+ feed.author do |author|
+ author.name 'Gentoo Packages Database'
+ end
+
+ @changes.each do |change|
+ atom = cp_to_atom change.category, change.package
+ package = Package.find_by :atom, atom
+ if package.nil?
+ logger.warn "Package for change (#{change}) nil!"
+ next
+ end
+
+ id = atom
+ id += '-%s' % change.version if change[:version]
+ id += '-%s' % change.arches.join(',') if change[:arches]
+
+ feed.entry(
+ change,
+ id: atom_id(@feed_type, @feed_id, id),
+ url: absolute_link_to_package(atom)) do |entry|
+ entry.updated change.created_at.to_datetime.rfc3339
+
+ case @feed_type
+ when :added
+ entry.title(t :feed_added_title,
+ atom: atom,
+ description: package.description)
+ entry.content(t :feed_added_content,
+ atom: atom,
+ arches: package.latest_version.keywords.join(', '))
+ when :updated
+ entry.title(t :feed_updated_title,
+ atom: atom_add_version(atom, change.version),
+ description: package.description)
+ entry.content(t :feed_updated_content,
+ atom: change.version)
+ when :stable
+ entry.title(t :feed_stable_title,
+ atom: atom_add_version(atom, change.version),
+ description: package.description)
+ entry.content(t :feed_stable_content,
+ atom: atom,
+ arches: change.arches.join(', '))
+ when :keyworded
+ entry.title(t :feed_keyworded_title,
+ atom: atom_add_version(atom, change.version),
+ description: package.description)
+ entry.content(t :feed_keyworded_content,
+ atom: atom,
+ arches: change.arches.join(', '))
+ end
+ end
+ end
+end
diff --git a/app/views/index/_package.html.erb b/app/views/index/_package.html.erb
new file mode 100644
index 0000000..431142a
--- /dev/null
+++ b/app/views/index/_package.html.erb
@@ -0,0 +1,8 @@
+<tr>
+ <td>
+ <a href="<%= slf(package_path(cp_to_atom(change.category, change.package))) %>">
+ <span class="text-muted"><%= change.category %></span>/<strong><%= change.package %><%= "-#{change.version}" if change[:version] %></strong>
+ </a>
+ </td>
+ <td><%= Package.find_by(:atom, cp_to_atom(change.category, change.package)).description %></td>
+</tr> \ No newline at end of file
diff --git a/app/views/index/index.html.erb b/app/views/index/index.html.erb
new file mode 100644
index 0000000..890a5f3
--- /dev/null
+++ b/app/views/index/index.html.erb
@@ -0,0 +1,53 @@
+<div class="jumbotron">
+ <h2 class="site-welcome stick-top">Welcome to the Home of <span class="text-primary"><%= number_with_delimiter Package.count %></span> Gentoo Packages</h2>
+
+ <form action="<%= search_packages_path %>" method="get">
+ <div class="typeahead-container">
+ <div class="typeahead-field">
+ <span class="typeahead-query">
+ <input id="q" name="q" type="search" autocomplete="off" placeholder="<%= t :find_packages %>" aria-label="<%= t :find_packages %>" autofocus>
+ </span>
+ <span class="typeahead-button">
+ <button type="submit" title="<%= t :find %>" aria-label="<%= t :find %>">
+ <span class="typeahead-search-icon"></span><span class="sr-only"><%= t :find %></span>
+ </button>
+ </span>
+ </div>
+ </div>
+ </form>
+</div>
+
+<% cache("added-#{@new_packages.hash}") do %>
+<div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">
+ <span class="fa fa-fw fa-history"></span>
+ <%= link_to t(:added_packages), added_packages_path %>
+ </h3>
+ </div>
+ <div class="table-responsive">
+ <table class="table table-striped">
+ <%= render partial: 'package', collection: @new_packages, as: 'change' %>
+ </table>
+ </div>
+</div>
+<% end %>
+
+<% cache("updated-#{@version_bumps.hash}") do %>
+<div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">
+ <span class="fa fa-fw fa-asterisk"></span>
+ <%= link_to t(:updated_packages), updated_packages_path %>
+ </h3>
+ </div>
+ <ul class="list-group">
+ <% @version_bumps.each do |change|
+ _package = Package.find_by(:atom, cp_to_atom(change.category, change.package)) %>
+ <%= render partial: 'packages/changed_package', object: change, as: 'change', locals: { package: _package, version: _package.version(change.version) } %>
+ <% end %>
+ </ul>
+</div>
+<% end %>
+
+<%= javascript_include_tag 'index/typeahead.js' %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
new file mode 100644
index 0000000..72fb8fa
--- /dev/null
+++ b/app/views/layouts/application.html.erb
@@ -0,0 +1,153 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title><%= "#{@title} – " if @title %>Gentoo Packages</title>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="theme-color" content="#54487a">
+ <meta name="description" content="<%= "#{@description} in the " if @description %>Gentoo Packages Database">
+ <link href="https://assets.gentoo.org/tyrian/bootstrap.min.css" rel="stylesheet" media="screen">
+ <link href="https://assets.gentoo.org/tyrian/tyrian.min.css" rel="stylesheet" media="screen">
+ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
+ <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
+ <script src="https://assets.gentoo.org/tyrian/bootstrap.min.js"></script>
+ <link rel="icon" href="https://www.gentoo.org/favicon.ico" type="image/x-icon">
+ <% if content_for? :head -%>
+ <%= yield :head %>
+ <% end -%>
+</head>
+<body class="kk">
+<header>
+ <div class="site-title">
+ <div class="container">
+ <div class="row">
+ <div class="site-title-buttons">
+ <div class="btn-group btn-group-sm">
+ <a href="https://get.gentoo.org/" role="button" class="btn get-gentoo"><span class="fa fa-fw fa-download"></span> <strong>Get Gentoo!</strong></a>
+ <div class="btn-group btn-group-sm">
+ <a class="btn gentoo-org-sites dropdown-toggle" data-toggle="dropdown" data-target="#" href="#">
+ <span class="fa fa-fw fa-map-o"></span> <span class="hidden-xs">gentoo.org sites</span> <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu dropdown-menu-right">
+ <li><a href="https://www.gentoo.org/" title="Main Gentoo website"><span class="fa fa-home fa-fw"></span> gentoo.org</a></li>
+ <li><a href="https://wiki.gentoo.org/" title="Find and contribute documentation"><span class="fa fa-file-text-o fa-fw"></span> Wiki</a></li>
+ <li><a href="https://bugs.gentoo.org/" title="Report issues and find common issues"><span class="fa fa-bug fa-fw"></span> Bugs</a></li>
+ <li><a href="https://forums.gentoo.org/" title="Discuss with the community"><span class="fa fa-comments-o fa-fw"></span> Forums</a></li>
+ <li><a href="https://packages.gentoo.org/" title="Find software for your Gentoo"><span class="fa fa-hdd-o fa-fw"></span> Packages</a></li>
+ <li class="divider"></li>
+ <li><a href="https://planet.gentoo.org/" title="Find out what's going on in the developer community"><span class="fa fa-rss fa-fw"></span> Planet</a></li>
+ <li><a href="https://archives.gentoo.org/" title="Read up on past discussions"><span class="fa fa-archive fa-fw"></span> Archives</a></li>
+ <li><a href="https://sources.gentoo.org/" title="Browse our source code"><span class="fa fa-code fa-fw"></span> Sources</a></li>
+ <li class="divider"></li>
+ <li><a href="https://infra-status.gentoo.org/" title="Get updates on the services provided by Gentoo"><span class="fa fa-server fa-fw"></span> Infra Status</a></li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ <div class="logo">
+ <a href="/" title="Back to the homepage" class="site-logo">
+ <object data="https://assets.gentoo.org/tyrian/site-logo.svg" type="image/svg+xml">
+ <img src="https://assets.gentoo.org/tyrian/site-logo.png" alt="Gentoo Linux Logo">
+ </object>
+ </a>
+ <span class="site-label">Packages</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <nav class="tyrian-navbar" role="navigation">
+ <div class="container">
+ <div class="row">
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-main-collapse">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ </div>
+ <div class="collapse navbar-collapse navbar-main-collapse">
+ <ul class="nav navbar-nav">
+ <li class="<%= 'active' if @nav == :index %>"><%= link_to t(:home), '/' %></li>
+ <li class="<%= 'active' if @nav == :packages %>"><%= link_to t(:packages), categories_path %></li>
+ <li class="<%= 'active' if @nav == :use %>"><%= link_to t(:use_flags), useflags_path %></li>
+ <li class="<%= 'active' if @nav == :arches %>"><%= link_to t(:architectures), arches_path %></li>
+ <li class="<%= 'active' if @nav == :about %>"><%= link_to t(:about), about_path %></li>
+ </ul>
+ <% unless @nav == :index %>
+ <form class="navbar-form navbar-right" role="search" action="<%= search_packages_path %>" method="get">
+ <div class="form-group">
+ <input type="text" class="form-control" placeholder="Find Packages" name="q">
+ </div>
+ </form>
+ <% end %>
+ </div>
+ </div>
+ </div>
+ </nav>
+</header>
+
+<div class="container">
+ <div class="row">
+ <div class="col-xs-12">
+ <%= yield %>
+ </div>
+ </div>
+</div>
+
+<footer>
+ <div class="container">
+ <div class="row">
+ <div class="col-xs-12 col-md-offset-2 col-md-7">
+ <h3 class="footerhead"><%= t :app_name %></h3>
+ <div class="row">
+ <div class="col-xs-12 col-md-4">
+ <span class="kk-group-header"><%= t :data_current_as_of %></span><br><%= last_import_start ? i18n_date(last_import_start) : 'unknown' %>
+ </div>
+ <div class="col-xs-12 col-md-4">
+ </div>
+ <div class="col-xs-12 col-md-4">
+ </div>
+ </div>
+ </div>
+ <div class="col-xs-12 col-md-3">
+ <h3 class="footerhead">Questions or comments?</h3>
+ Please feel free to <a href="https://www.gentoo.org/inside-gentoo/contact/">contact us</a>.
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-xs-2 col-sm-3 col-md-2">
+ <ul class="footerlinks three-icons">
+ <li><a href="https://twitter.com/gentoo" title="@Gentoo on Twitter"><span class="fa fa-twitter fa-fw"></span></a></li>
+ <li><a href="https://plus.google.com/+Gentoo" title="+Gentoo on Google+"><span class="fa fa-google-plus fa-fw"></span></a></li>
+ <li><a href="https://www.facebook.com/gentoo.org" title="Gentoo on Facebook"><span class="fa fa-facebook fa-fw"></span></a></li>
+ </ul>
+ </div>
+ <div class="col-xs-10 col-sm-9 col-md-10">
+ <strong>&copy; 2001&ndash;2016 Gentoo Foundation, Inc.</strong><br>
+ <small>
+ Gentoo is a trademark of the Gentoo Foundation, Inc.
+ The contents of this document, unless otherwise expressly stated, are licensed under the
+ <a href="https://creativecommons.org/licenses/by-sa/3.0/" rel="license">CC-BY-SA-3.0</a> license.
+ The <a href="https://www.gentoo.org/inside-gentoo/foundation/name-logo-guidelines.html">Gentoo Name and Logo Usage Guidelines</a> apply.
+ </small>
+ </div>
+ </div>
+ </div>
+</footer>
+<script type="text/javascript">
+ var _paq = _paq || [];
+ _paq.push(['disableCookies']);
+ _paq.push(['trackPageView']);
+ _paq.push(['enableLinkTracking']);
+ (function() {
+ var u="//piwik.gentoo.org/";
+ _paq.push(['setTrackerUrl', u+'piwik.php']);
+ _paq.push(['setSiteId', 8]);
+ var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
+ g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
+ })();
+</script>
+<noscript><p><img src="//piwik.gentoo.org/piwik.php?idsite=8" style="border:0;" alt="" /></p></noscript>
+</body>
+</html>
diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb
new file mode 100644
index 0000000..991cf0f
--- /dev/null
+++ b/app/views/layouts/mailer.html.erb
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ <%= yield %>
+ </body>
+</html>
diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb
new file mode 100644
index 0000000..37f0bdd
--- /dev/null
+++ b/app/views/layouts/mailer.text.erb
@@ -0,0 +1 @@
+<%= yield %>
diff --git a/app/views/packages/_changed_package.html.erb b/app/views/packages/_changed_package.html.erb
new file mode 100644
index 0000000..2e917e7
--- /dev/null
+++ b/app/views/packages/_changed_package.html.erb
@@ -0,0 +1,71 @@
+<% unless version.nil? %>
+<li class="list-group-item kk-package-detailed">
+ <div class="row">
+ <div class="col-xs-12 col-md-6">
+ <h4 class="stick-top"><%= link_to package.atom, slf(package_path(package.atom)) %></h4>
+ <div class="kk-package-detailed-toolbox">
+ <div class="btn-group">
+ <button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ <span class="fa fa-fw fa-navicon"></span>
+ </button>
+ <ul class="dropdown-menu dropdown-menu-right">
+ <li><a href="https://bugs.gentoo.org/buglist.cgi?quicksearch=<%= u package.atom %>" target="_blank">
+ <span class="fa fa-fw fa-bug"></span>
+ <%= t :res_bugs %>
+ </a></li>
+ <li><a href="https://wiki.gentoo.org/index.php?title=Special%3ASearch&fulltext=Search&search=<%= u package.name %>" target="_blank">
+ <span class="fa fa-fw fa-book"></span>
+ <%= t :res_docs %>
+ </a></li>
+ <li><a href="https://forums.gentoo.org/search.php?search_terms=all&show_results=topics&search_keywords=<%= u package.name %>&mode=results" target="_blank">
+ <span class="fa fa-fw fa-comments-o"></span>
+ <%= t :res_forums %>
+ </a></li>
+ <li role="separator" class="divider"></li>
+ <li><a href="https://gitweb.gentoo.org/repo/gentoo.git/tree/<%= package.atom %>" target="_blank">
+ <span class="fa fa-fw fa-code-fork"></span>
+ <%= t :res_repo %>
+ </a></li>
+ <li><a href="https://gitweb.gentoo.org/repo/gentoo.git/log/<%= package.atom %>?showmsg=1" target="_blank">
+ <span class="fa fa-fw fa-history"></span>
+ <%= t :res_log %>
+ </a></li>
+ <li><a href="https://gitweb.gentoo.org/repo/gentoo.git/atom/<%= package.atom %>?h=master" target="_blank">
+ <span class="fa fa-fw fa-rss"></span>
+ <%= t :res_feed %>
+ </a></li>
+ <li role="separator" class="divider"></li>
+ <li><a href="http://www.portagefilelist.de/site/query/listPackageVersions/?category=<%= package.category %>&package=<%= package.name %>&do#result" target="_blank">
+ <span class="fa fa-fw fa-files-o"></span>
+ <%= t :res_installed_files %> <small>(via PFL<span class="fa fa-fw fa-external-link-square"></span>)</small>
+ </a></li>
+ </ul>
+ </div>
+ </div>
+ <%= package.description %>
+ <br>
+ <small class="text-muted">
+ <% unless change.arches == nil or change.arches.empty? %>
+ <%= t :added_keywords, keywords: change.arches.join(', ') %>
+ <% end %>
+ </small>
+ <% unless (changelog_entry = matching_changelog_entry(change)).nil? %>
+ <div class="kk-inline-changelog-entry">
+ <a href="<%= gitweb_commit_url(changelog_entry[:id]) %>" title="<%= t :git_commit %>">
+ <span class="octicon octicon-git-pull-request"></span>
+ <span class="kk-commit-message">
+ <%= changelog_entry[:message].lines.first %>
+ </span>
+ </a>
+ </div>
+ <% end %>
+ </div>
+ <div class="col-xs-12 col-md-6">
+ <small class="text-muted pull-right">
+ <%= i18n_date(change.created_at) %>
+ </small>
+ <%= render partial: 'packages/version_card', object: version, as: 'version' %>
+ </div>
+ </div>
+</li>
+<% end %>
diff --git a/app/views/packages/_changelog.html.erb b/app/views/packages/_changelog.html.erb
new file mode 100644
index 0000000..c469a50
--- /dev/null
+++ b/app/views/packages/_changelog.html.erb
@@ -0,0 +1,19 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">Changelog</h3>
+ </div>
+ <ul class="list-group" id="changelog-container">
+ <li class="list-group-item kk-panel-content-sorry">
+ <span class="fa fa-refresh fa-spin fa-3x"></span>
+ <noscript>
+ <br><br>
+ <%= t :changelog_error %>
+ <br><br>
+ <a href="https://gitweb.gentoo.org/repo/gentoo.git/log/<%= @package.atom %>?showmsg=1" class="btn btn-default">
+ <span class="fa fa-fw fa-history"></span>
+ <%= t :view_git_changelog %>
+ </a>
+ </noscript>
+ </li>
+ </ul>
+</div>
diff --git a/app/views/packages/_changelog_entry.html.erb b/app/views/packages/_changelog_entry.html.erb
new file mode 100644
index 0000000..6b7b1da
--- /dev/null
+++ b/app/views/packages/_changelog_entry.html.erb
@@ -0,0 +1,31 @@
+<li class="list-group-item">
+ <strong><%= annotate_bugs changelog[:message].lines.first %></strong>
+ <br>
+ <div class="kk-byline">
+ <%= mail_to changelog[:email], changelog[:author] %>,
+ <%= i18n_date(changelog[:date]) %>,
+ commit&nbsp;<%= link_to_gitweb_commit changelog[:id]%>
+ </div>
+
+ <table class="table table-condensed kk-changelog-diffstat">
+ <% unless changelog[:files][:added].empty? %>
+ <tr class="success">
+ <td class="kk-changelog-diffstat-icon"><span class="octicon octicon-diff-added"></span></td>
+ <td><%= safe_join(changelog[:files][:added].map {|f| link_to_gitweb_ebuild_diff(f, changelog[:id], @package.category, @package.name) }, ', ') %></td>
+ </tr>
+ <% end %>
+ <% unless changelog[:files][:modified].empty? %>
+ <tr class="warning">
+ <td class="kk-changelog-diffstat-icon"><span class="octicon octicon-diff-modified"></span></td>
+ <td><%= safe_join(changelog[:files][:modified].map {|f| link_to_gitweb_ebuild_diff(f, changelog[:id], @package.category, @package.name) }, ', ') %></td>
+ </tr>
+ <% end %>
+ <% unless changelog[:files][:deleted].empty? %>
+ <tr class="danger">
+ <td class="kk-changelog-diffstat-icon"><span class="octicon octicon-diff-removed"></span></td>
+ <td><%= safe_join(changelog[:files][:deleted].map {|f| link_to_gitweb_ebuild_diff(f, changelog[:id], @package.category, @package.name) }, ', ') %></td>
+ </tr>
+ <% end %>
+
+ </table>
+</li>
diff --git a/app/views/packages/_herd.html.erb b/app/views/packages/_herd.html.erb
new file mode 100644
index 0000000..3e0db76
--- /dev/null
+++ b/app/views/packages/_herd.html.erb
@@ -0,0 +1 @@
+<%= link_to_herd herd %>
diff --git a/app/views/packages/_keyword_legend.html.erb b/app/views/packages/_keyword_legend.html.erb
new file mode 100644
index 0000000..8c45e36
--- /dev/null
+++ b/app/views/packages/_keyword_legend.html.erb
@@ -0,0 +1,17 @@
+<ul class="list-group kk-keyword-legend">
+ <li class="list-group-item kk-keyword-stable">
+ <%= keyword_icon_tag :stable %> &nbsp;<%= t :legend_stable %>
+ </li>
+ <li class="list-group-item kk-keyword-testing">
+ <%= keyword_icon_tag :testing %> &nbsp;<%= t :legend_testing %>
+ </li>
+ <li class="list-group-item kk-keyword-unavailable">
+ <%= keyword_icon_tag :unavailable %> &nbsp;<%= t :legend_unavailable %>
+ </li>
+ <li class="list-group-item kk-keyword-masked">
+ <%= keyword_icon_tag :masked %> &nbsp;<%= t :legend_masked %>
+ </li>
+ <li class="list-group-item kk-keyword-unknown">
+ <span class="kk-octicon-spacer"></span>&nbsp;<%= t :legend_unknown %>
+ </li>
+</ul>
diff --git a/app/views/packages/_maintainer.html.erb b/app/views/packages/_maintainer.html.erb
new file mode 100644
index 0000000..9f89c7c
--- /dev/null
+++ b/app/views/packages/_maintainer.html.erb
@@ -0,0 +1 @@
+<%= mail_to maintainer['email'], maintainer['name'], title: maintainer['email'] %> \ No newline at end of file
diff --git a/app/views/packages/_maintainer_needed_notice.html.erb b/app/views/packages/_maintainer_needed_notice.html.erb
new file mode 100644
index 0000000..8359547
--- /dev/null
+++ b/app/views/packages/_maintainer_needed_notice.html.erb
@@ -0,0 +1,7 @@
+<% if package.needs_maintainer? %>
+ <div class="alert alert-info">
+ <strong><span class="fa fa-fw fa-wrench"></span> This package needs a new maintainer!</strong><br>
+ If you are interested in helping with the maintenance of <%= package.name %>, please get in touch with our
+ <a href="https://wiki.gentoo.org/wiki/Project:Proxy_Maintainers" class="alert-link">Proxy Maintainers team</a>.
+ </div>
+<% end %> \ No newline at end of file
diff --git a/app/views/packages/_maintainer_spacer.html.erb b/app/views/packages/_maintainer_spacer.html.erb
new file mode 100644
index 0000000..4bb959b
--- /dev/null
+++ b/app/views/packages/_maintainer_spacer.html.erb
@@ -0,0 +1 @@
+<%= ', ' %> \ No newline at end of file
diff --git a/app/views/packages/_mask.html.erb b/app/views/packages/_mask.html.erb
new file mode 100644
index 0000000..b76a103
--- /dev/null
+++ b/app/views/packages/_mask.html.erb
@@ -0,0 +1,30 @@
+<li class="list-group-item kk-mask">
+ <strong class="kk-mask-reason text-danger"><%= annotate_bugs(mask['reason']) %></strong>
+
+ <div class="kk-mask-details">
+ <!--<div class="row">
+ <div class="col-xs-12 col-md-3 kk-metadata-key">
+ Affected architectures
+ </div>
+ <div class="col-xs-12 col-md-9 kk-mask-atoms">
+ <%# mask['arch'] %>
+ </div>
+ </div>-->
+ <div class="row">
+ <div class="col-xs-12 col-md-3 kk-metadata-key">
+ <%= t :mask_packages %>
+ </div>
+ <div class="col-xs-12 col-md-9 kk-mask-atoms">
+ <%= mask['atoms'].join ', ' %>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-xs-12 col-md-3 kk-metadata-key">
+ <%= t :mask_author %>
+ </div>
+ <div class="col-xs-12 col-md-9">
+ <%= mask['author'] %> <span class="text-muted">(<%= mask['date'] %>)</span>
+ </div>
+ </div>
+ </div>
+</li>
diff --git a/app/views/packages/_masks.html.erb b/app/views/packages/_masks.html.erb
new file mode 100644
index 0000000..89bf166
--- /dev/null
+++ b/app/views/packages/_masks.html.erb
@@ -0,0 +1,10 @@
+<% unless (_masks = filter_masks(versions)).empty? %>
+<div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><%= t :box_masks %></h3>
+ </div>
+ <ul class="list-group">
+ <%= render partial: 'mask', collection: _masks.to_a, as: 'mask' %>
+ </ul>
+</div>
+<% end %>
diff --git a/app/views/packages/_metadata.html.erb b/app/views/packages/_metadata.html.erb
new file mode 100644
index 0000000..426afd9
--- /dev/null
+++ b/app/views/packages/_metadata.html.erb
@@ -0,0 +1,87 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><%= t :box_metadata %></h3>
+ </div>
+ <ul class="list-group kk-metadata-list">
+ <% if package.homepage.size > 1 %>
+ <li class="kk-metadata-item list-group-item">
+ <div class="row">
+ <div class="col-xs-12 col-md-3 kk-metadata-key">
+ <span class="fa fa-fw fa-home"></span>
+ <%= t :other_homepages %>
+ </div>
+ <div class="col-xs-12 col-md-9">
+ <% package.homepage[1..-1].each do |hp| %>
+ <%= link_to hp, hp, rel: 'nofollow' %>
+ <% end %>
+ </div>
+ </div>
+ </li>
+ <% end %>
+ <% if package.longdescription %>
+ <li class="kk-metadata-item list-group-item">
+ <div class="row">
+ <div class="col-xs-12 col-md-3 kk-metadata-key">
+ <span class="fa fa-fw fa-info"></span>
+ <%= t :longdescription %>
+ </div>
+ <div class="col-xs-12 col-md-9">
+ <%= package.longdescription %>
+ </div>
+ </div>
+ </li>
+ <% end %>
+ <% if package.has_useflags? %>
+ <li class="kk-metadata-item list-group-item">
+ <div class="row">
+ <div class="col-xs-12 col-md-3 kk-metadata-key">
+ <span class="fa fa-fw fa-sliders"></span>
+ <%= t :use_flags %>
+ </div>
+ <div class="col-xs-12 col-md-9">
+ <%= render partial: 'metadata_use', object: package.useflags, as: 'useflags' %>
+ </div>
+ </div>
+ </li>
+ <% end %>
+ <% if package.license %>
+ <li class="kk-metadata-item list-group-item">
+ <div class="row">
+ <div class="col-xs-12 col-md-3 kk-metadata-key">
+ <span class="fa fa-fw fa-legal"></span>
+ <%= t :license %>
+ </div>
+ <div class="col-xs-12 col-md-9">
+ <%= annotate_license_str package.license %>
+ </div>
+ </div>
+ </li>
+ <% end %>
+ <% if package.herds and package.herds.size > 0 %>
+ <li class="kk-metadata-item list-group-item">
+ <div class="row">
+ <div class="col-xs-12 col-md-3 kk-metadata-key">
+ <span class="fa fa-fw fa-group"></span>
+ <%= t :herds %>
+ </div>
+ <div class="col-xs-12 col-md-9">
+ <%= render partial: 'herd', collection: package.herds.sort, as: 'herd', spacer_template: 'maintainer_spacer' %>
+ </div>
+ </div>
+ </li>
+ <% end %>
+ <% if package.maintainers and package.maintainers.size > 0 %>
+ <li class="kk-metadata-item list-group-item">
+ <div class="row">
+ <div class="col-xs-12 col-md-3 kk-metadata-key">
+ <span class="fa fa-fw fa-user"></span>
+ <%= t :maintainers %>
+ </div>
+ <div class="col-xs-12 col-md-9">
+ <%= render partial: 'maintainer', collection: package.maintainers, as: 'maintainer', spacer_template: 'maintainer_spacer' %>
+ </div>
+ </div>
+ </li>
+ <% end %>
+ </ul>
+</div>
diff --git a/app/views/packages/_metadata_use.html.erb b/app/views/packages/_metadata_use.html.erb
new file mode 100644
index 0000000..d33b751
--- /dev/null
+++ b/app/views/packages/_metadata_use.html.erb
@@ -0,0 +1,14 @@
+<% unless useflags['local'].empty? %>
+ <span class="kk-useflag-group"><%= t :local_use_flags %></span>
+ <%= render partial: 'useflag', object: useflags['local'], as: 'useflags' %>
+<% end %>
+<% unless useflags['global'].empty? %>
+ <span class="kk-useflag-group"><%= t :global_use_flags %></span>
+ <%= render partial: 'useflag', object: useflags['global'], as: 'useflags' %>
+<% end %>
+<% unless useflags['use_expand'].empty? %>
+ <% useflags['use_expand'].each_pair do |flag, values| %>
+ <span class="kk-useflag-group"><%= t :use_expand_flag, flag: flag %></span>
+ <%= render partial: 'useflag', object: values, as: 'useflags' %>
+ <% end %>
+<% end %>
diff --git a/app/views/packages/_package_header.html.erb b/app/views/packages/_package_header.html.erb
new file mode 100644
index 0000000..5b8ae08
--- /dev/null
+++ b/app/views/packages/_package_header.html.erb
@@ -0,0 +1,31 @@
+<ol class="breadcrumb">
+ <li><a href="/">Home</a></li>
+ <li><%= link_to t(:packages), categories_path %></li>
+ <li><%= link_to package.category_model.name, category_path(package.category_model) %></li>
+ <li class="active"><%= package.name %></li>
+</ol>
+
+<div class="row">
+ <div class="col-md-4">
+ <h1 class="stick-top kk-package-title" id="package-title" data-atom="<%= package.atom %>" data-category="<%= package.category %>" data-name="<%= package.name %>">
+ <small class="kk-package-cat"><%= package.category_model.name %>/</small>
+ <div>
+ <span class="mega-octicon octicon-package kk-package-icon"></span>
+ <div class="kk-package-name"><%= package.name %></div>
+ </div>
+ </h1>
+ </div>
+ <div class="col-md-8">
+ <p class="lead kk-package-maindesc">
+ <%= package.description %>
+ </p>
+
+ <p class="kk-package-homepage">
+ <% if '' != package.homepage.first %>
+ <span class="fa fa-fw fa-home"></span> <%= link_to package.homepage.first, package.homepage.first, rel: 'nofollow' %>
+ <% end %>
+ </p>
+ </div>
+</div>
+
+<hr>
diff --git a/app/views/packages/_package_result_row.html.erb b/app/views/packages/_package_result_row.html.erb
new file mode 100644
index 0000000..fd6c903
--- /dev/null
+++ b/app/views/packages/_package_result_row.html.erb
@@ -0,0 +1,4 @@
+<a class="list-group-item" href="<%= slf package_path package %>">
+ <h3 class="kk-search-result-header"><span class="text-muted"><%= package.category %>/</span><%= package.name %></h3>
+ <%= package.description %>
+</a> \ No newline at end of file
diff --git a/app/views/packages/_removal_notice.html.erb b/app/views/packages/_removal_notice.html.erb
new file mode 100644
index 0000000..20d226b
--- /dev/null
+++ b/app/views/packages/_removal_notice.html.erb
@@ -0,0 +1,5 @@
+<div class="alert alert-danger">
+ <strong><span class="fa fa-fw fa-warning"></span> This package is masked and could be removed soon!</strong><br>
+ The mask comment indicates that this package is scheduled for removal from our package repository.<br>
+ Please review the mask information below for more details.
+</div> \ No newline at end of file
diff --git a/app/views/packages/_resources.html.erb b/app/views/packages/_resources.html.erb
new file mode 100644
index 0000000..51576a9
--- /dev/null
+++ b/app/views/packages/_resources.html.erb
@@ -0,0 +1,35 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><%= t(:resources) %></h3>
+ </div>
+ <div class="list-group">
+ <a href="https://bugs.gentoo.org/buglist.cgi?quicksearch=<%= u package.atom %>" class="list-group-item" target="_blank">
+ <span class="fa fa-fw fa-bug"></span>
+ <%= t :res_bugs %>
+ </a>
+ <a href="https://wiki.gentoo.org/index.php?title=Special%3ASearch&fulltext=Search&search=<%= u package.name %>" class="list-group-item" target="_blank">
+ <span class="fa fa-fw fa-book"></span>
+ <%= t :res_docs %>
+ </a>
+ <a href="https://forums.gentoo.org/search.php?search_terms=all&show_results=topics&search_keywords=<%= u package.name %>&mode=results" class="list-group-item" target="_blank">
+ <span class="fa fa-fw fa-comments-o"></span>
+ <%= t :res_forums %>
+ </a>
+ <a href="https://gitweb.gentoo.org/repo/gentoo.git/tree/<%= package.atom %>" class="list-group-item" target="_blank">
+ <span class="fa fa-fw fa-code-fork"></span>
+ <%= t :res_repo %>
+ </a>
+ <a href="https://gitweb.gentoo.org/repo/gentoo.git/log/<%= package.atom %>?showmsg=1" class="list-group-item" target="_blank">
+ <span class="fa fa-fw fa-history"></span>
+ <%= t :res_log %>
+ </a>
+ <a href="https://gitweb.gentoo.org/repo/gentoo.git/atom/<%= package.atom %>?h=master" class="list-group-item" target="_blank">
+ <span class="fa fa-fw fa-rss"></span>
+ <%= t :res_feed %>
+ </a>
+ <a href="http://www.portagefilelist.de/site/query/listPackageVersions/?category=<%= package.category %>&package=<%= package.name %>&do#result" class="list-group-item" target="_blank">
+ <span class="fa fa-fw fa-files-o"></span>
+ <%= t :res_installed_files %> <small>(via PFL<span class="fa fa-fw fa-external-link-square"></span>)</small>
+ </a>
+ </div>
+</div>
diff --git a/app/views/packages/_useflag.html.erb b/app/views/packages/_useflag.html.erb
new file mode 100644
index 0000000..a60e589
--- /dev/null
+++ b/app/views/packages/_useflag.html.erb
@@ -0,0 +1,5 @@
+<ul class="kk-useflag-container <%= useflags.size > 10 ? 'kk-useflag-container-many' : 'kk-useflag-container-few' %>">
+<% useflags.each_pair do |flag, flag_data| %>
+ <li class="kk-useflag"><%= link_to flag, useflag_path(id: flag_data['name']), :title => strip_tags(flag_data['description']), 'data-toggle' => 'tooltip' %></li>
+<% end %>
+</ul>
diff --git a/app/views/packages/_version_card.html.erb b/app/views/packages/_version_card.html.erb
new file mode 100644
index 0000000..7045617
--- /dev/null
+++ b/app/views/packages/_version_card.html.erb
@@ -0,0 +1,14 @@
+<div class="kk-version-card">
+ <p><strong><%= version.version %></strong><%= version_slot version.slot %> <%= version_labels version %></p>
+ <p>
+ <%= keyword_label version, 'amd64' %>
+ <%= keyword_label version, 'x86' %>
+ <%= keyword_label version, 'alpha' %>
+ <%= keyword_label version, 'arm' %>
+ <%= keyword_label version, 'hppa' %>
+ <%= keyword_label version, 'ia64' %>
+ <%= keyword_label version, 'ppc' %>
+ <%= keyword_label version, 'ppc64' %>
+ <%= keyword_label version, 'sparc' %>
+ </p>
+</div>
diff --git a/app/views/packages/_version_row.html.erb b/app/views/packages/_version_row.html.erb
new file mode 100644
index 0000000..d30d974
--- /dev/null
+++ b/app/views/packages/_version_row.html.erb
@@ -0,0 +1,12 @@
+<tr>
+ <td class="kk-version kk-cell-sep-right"><strong><%= version.version %></strong><%= version_slot version.slot, version.subslot %> <%= version_labels version %></td>
+ <%= keyword_cell version, 'amd64' %>
+ <%= keyword_cell version, 'x86', true %>
+ <%= keyword_cell version, 'alpha' %>
+ <%= keyword_cell version, 'arm' %>
+ <%= keyword_cell version, 'hppa' %>
+ <%= keyword_cell version, 'ia64' %>
+ <%= keyword_cell version, 'ppc' %>
+ <%= keyword_cell version, 'ppc64' %>
+ <%= keyword_cell version, 'sparc' %>
+</tr>
diff --git a/app/views/packages/_versions.html.erb b/app/views/packages/_versions.html.erb
new file mode 100644
index 0000000..3484dc4
--- /dev/null
+++ b/app/views/packages/_versions.html.erb
@@ -0,0 +1,38 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">
+ <%= t :box_versions %>
+ <span class="pull-right">
+ <a href="<%= about_help_path(anchor: 'keyword-legend') %>" aria-label="<%= t :show_table_legend %>" title="<%= t :legend %>" class="kk-box-meta-link" tabindex="0" role="button" id="kk-keyword-legend-btn">
+ <span class="fa fa-fw fa-question-circle"></span>
+ </a>
+ </span>
+ </h3>
+ </div>
+ <div class="table-responsive">
+ <table class="table table-bordered kk-versions-table">
+ <thead>
+ <tr>
+ <th class="kk-version kk-cell-sep-right"><%= t :version %></th>
+ <th class="kk-keyword-header kk-keyword">amd64</th>
+ <th class="kk-keyword-header kk-keyword kk-cell-sep-right">x86</th>
+ <th class="kk-keyword-header kk-keyword">alpha</th>
+ <th class="kk-keyword-header kk-keyword">arm</th>
+ <th class="kk-keyword-header kk-keyword">hppa</th>
+ <th class="kk-keyword-header kk-keyword">ia64</th>
+ <th class="kk-keyword-header kk-keyword">ppc</th>
+ <th class="kk-keyword-header kk-keyword">ppc64</th>
+ <th class="kk-keyword-header kk-keyword">sparc</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <%= render partial: 'version_row', collection: versions, as: 'version' %>
+ </tbody>
+ </table>
+ </div>
+</div>
+
+<script id="kk-keyword-legend-text" type="text/template">
+<%= render partial: 'keyword_legend' %>
+</script>
diff --git a/app/views/packages/added.html.erb b/app/views/packages/added.html.erb
new file mode 100644
index 0000000..97d5cb6
--- /dev/null
+++ b/app/views/packages/added.html.erb
@@ -0,0 +1,23 @@
+<ol class="breadcrumb">
+ <li><a href="/">Home</a></li>
+ <li><%= link_to t(:packages), categories_path %></li>
+ <li class="active"><%= t :added_packages %></li>
+</ol>
+
+<h1>
+ <%= t :added_packages %>
+ <%= feed_icon added_packages_path(format: :atom) %>
+</h1>
+
+<% cache("added-full-#{@changes.hash}") do %>
+ <ul class="list-group">
+ <% @changes.each do |change|
+ _package = Package.find_by(:atom, cp_to_atom(change.category, change.package)) %>
+ <%= render partial: 'changed_package', object: change, as: 'change', locals: { package: _package, version: _package.latest_version } %>
+ <% end %>
+ </ul>
+<% end %>
+
+<% content_for :head do %>
+ <%= alternate_feed_link(added_packages_url(format: :atom), t(:atom_feed)) %>
+<% end %>
diff --git a/app/views/packages/changelog.html.erb b/app/views/packages/changelog.html.erb
new file mode 100644
index 0000000..df9242e
--- /dev/null
+++ b/app/views/packages/changelog.html.erb
@@ -0,0 +1,12 @@
+<% if @changelog.empty? -%>
+ <li class="list-group-item kk-panel-content-sorry">
+ <%= t :changelog_empty %>
+ <br><br>
+ <a href="https://sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-x86/<%= @package.atom %>/ChangeLog?view=markup" class="btn btn-default">
+ <span class="fa fa-fw fa-history"></span>
+ <%= t :view_cvs_changelog %>
+ </a>
+ </li>
+<% else %>
+ <%= render partial: 'changelog_entry', collection: @changelog, as: 'changelog' %>
+<% end %>
diff --git a/app/views/packages/changelog.json.jbuilder b/app/views/packages/changelog.json.jbuilder
new file mode 100644
index 0000000..a88a2db
--- /dev/null
+++ b/app/views/packages/changelog.json.jbuilder
@@ -0,0 +1 @@
+json.changes @changelog
diff --git a/app/views/packages/keyworded.html.erb b/app/views/packages/keyworded.html.erb
new file mode 100644
index 0000000..ff5b60c
--- /dev/null
+++ b/app/views/packages/keyworded.html.erb
@@ -0,0 +1,23 @@
+<ol class="breadcrumb">
+ <li><a href="/">Home</a></li>
+ <li><%= link_to t(:packages), categories_path %></li>
+ <li class="active"><%= t :keyworded_packages %></li>
+</ol>
+
+<h1>
+ <%= t :keyworded_packages %>
+ <%= feed_icon keyworded_packages_path(format: :atom) %>
+</h1>
+
+<% cache("keyworded-full-#{@changes.hash}") do %>
+ <ul class="list-group">
+ <% @changes.each do |change|
+ _package = Package.find_by(:atom, cp_to_atom(change.category, change.package)) %>
+ <%= render partial: 'changed_package', object: change, as: 'change', locals: { package: _package, version: _package.version(change.version) } %>
+ <% end %>
+ </ul>
+<% end %>
+
+<% content_for :head do %>
+ <%= alternate_feed_link(keyworded_packages_url(format: :atom), t(:atom_feed)) %>
+<% end %>
diff --git a/app/views/packages/resolve.json.jbuilder b/app/views/packages/resolve.json.jbuilder
new file mode 100644
index 0000000..73ffdbb
--- /dev/null
+++ b/app/views/packages/resolve.json.jbuilder
@@ -0,0 +1,4 @@
+json.packages @packages do |package|
+ json.extract! package, :atom, :description
+ json.href slf package_url(id: package.atom)
+end
diff --git a/app/views/packages/search.html.erb b/app/views/packages/search.html.erb
new file mode 100644
index 0000000..fe77dd3
--- /dev/null
+++ b/app/views/packages/search.html.erb
@@ -0,0 +1,38 @@
+<h1 class="first-header">Search Results <small>for <%= params[:q] %></small></h1>
+
+<% if @packages.size > 0 %>
+<div class="panel panel-default">
+ <div class="panel-heading">
+ Results <%= @offset + 1 %>—<%= [@offset + Package.default_search_size, @packages.total].min %> of <%= @packages.total %>
+ </div>
+ <div class="list-group">
+ <%= render partial: 'package_result_row', collection: @packages, as: 'package' %>
+ </div>
+ <div class="panel-footer">
+ <div class="btn-group" role="group" aria-label="Result navigation">
+ <%= link_to '< Prev', search_packages_path(q: params[:q], o: [@offset - Package.default_search_size, 0].max), class: 'btn btn-default' + (@offset > 0 ? '' : ' disabled') %>
+ <%= link_to 'Next >', search_packages_path(q: params[:q], o: @offset + Package.default_search_size), class: 'btn btn-default ' + ((@offset + Package.default_search_size) > @packages.total ? 'disabled' : '') %>
+ </div>
+ </div>
+</div>
+<% else %>
+<div class="jumbotron">
+ <h2 class="site-welcome stick-top">Nothing found. :( Try again?</h2>
+
+ <form action="<%= search_packages_path %>" method="get">
+ <div class="typeahead-container">
+ <div class="typeahead-field">
+ <span class="typeahead-query">
+ <input id="q" name="q" type="search" autocomplete="off" placeholder="<%= t :find_packages %>" aria-label="<%= t :find_packages %>" value="<%= params[:q] %>">
+ </span>
+ <span class="typeahead-button">
+ <button type="submit" title="<%= t :find %>" aria-label="<%= t :find %>">
+ <span class="typeahead-search-icon"></span><span class="sr-only"><%= t :find %></span>
+ </button>
+ </span>
+ </div>
+ </div>
+ </form>
+</div>
+<%= javascript_include_tag 'index/typeahead.js' %>
+<% end %>
diff --git a/app/views/packages/show.html.erb b/app/views/packages/show.html.erb
new file mode 100644
index 0000000..bc649ba
--- /dev/null
+++ b/app/views/packages/show.html.erb
@@ -0,0 +1,28 @@
+<% cache "#{@package.atom}-#{@package.metadata_hash}" do %>
+<%= render partial: 'package_header', object: @package, as: 'package' %>
+
+<div class="row">
+ <div class="col-md-9">
+ <%= render partial: 'versions', object: @package.versions, as: 'versions' %>
+
+ <% if @package.removal_pending? %>
+ <%= render partial: 'removal_notice', object: @package, as: 'package' %>
+ <% end %>
+
+ <%= render partial: 'maintainer_needed_notice', object: @package, as: 'package' %>
+ <%= render partial: 'metadata', object: @package, as: 'package', locals: { latest_version: @package.versions.first} %>
+
+ <%= render partial: 'masks', object: @package.versions, as: 'versions' %>
+
+ <%= render partial: 'changelog', object: @changelog, as: 'changelog' %>
+ </div>
+ <div class="col-md-3">
+ <%= render partial: 'resources', object: @package, as: 'package' %>
+ </div>
+</div>
+
+<%= javascript_include_tag 'packages/show' %>
+<% content_for :head do %>
+ <%= alternate_feed_link('https://gitweb.gentoo.org/repo/gentoo.git/atom/%s?h=master' % @package.atom, t(:raw_git_feed)) %>
+<% end %>
+<% end %>
diff --git a/app/views/packages/show.json.jbuilder b/app/views/packages/show.json.jbuilder
new file mode 100644
index 0000000..3b8a012
--- /dev/null
+++ b/app/views/packages/show.json.jbuilder
@@ -0,0 +1,43 @@
+json.extract! @package, :atom, :description
+json.href slf package_url(id: @package.atom)
+
+json.versions @package.versions do |version|
+ json.version version.version
+ json.keywords version.keywords
+ json.masks version.masks
+end
+
+json.herds @package.herds
+json.maintainers @package.maintainers do |maintainer|
+ json.email maintainer['email']
+ json.name maintainer['name']
+ json.description maintainer['description']
+ json.type maintainer['type']
+
+ if maintainer['type'] == 'project'
+ json.members project_members(maintainer['email'])
+ end
+end
+
+json.use do
+ json.local @package.versions.first.useflags[:local] do |flag|
+ json.name flag[1][:name]
+ json.description strip_tags flag[1][:description]
+ end
+
+ json.global @package.versions.first.useflags[:global] do |flag|
+ json.name flag[1][:name]
+ json.description strip_tags flag[1][:description]
+ end
+
+ json.use_expand @package.versions.first.useflags[:use_expand] do |flag|
+ json.set! flag[0] do
+ json.array! flag[1] do |expand_flag|
+ json.name expand_flag[0]
+ json.description strip_tags expand_flag[1][:description]
+ end
+ end
+ end
+end
+
+json.extract! @package, :updated_at
diff --git a/app/views/packages/stable.html.erb b/app/views/packages/stable.html.erb
new file mode 100644
index 0000000..7b230fe
--- /dev/null
+++ b/app/views/packages/stable.html.erb
@@ -0,0 +1,23 @@
+<ol class="breadcrumb">
+ <li><a href="/">Home</a></li>
+ <li><%= link_to t(:packages), categories_path %></li>
+ <li class="active"><%= t :stable_packages %></li>
+</ol>
+
+<h1>
+ <%= t :stable_packages %>
+ <%= feed_icon stable_packages_path(format: :atom) %>
+</h1>
+
+<% cache("stable-full-#{@changes.hash}") do %>
+ <ul class="list-group">
+ <% @changes.each do |change|
+ _package = Package.find_by(:atom, cp_to_atom(change.category, change.package)) %>
+ <%= render partial: 'changed_package', object: change, as: 'change', locals: { package: _package, version: _package.version(change.version) } %>
+ <% end %>
+ </ul>
+<% end %>
+
+<% content_for :head do %>
+ <%= alternate_feed_link(stable_packages_url(format: :atom), t(:atom_feed)) %>
+<% end %>
diff --git a/app/views/packages/suggest.json.jbuilder b/app/views/packages/suggest.json.jbuilder
new file mode 100644
index 0000000..fcd8ba5
--- /dev/null
+++ b/app/views/packages/suggest.json.jbuilder
@@ -0,0 +1 @@
+json.results @packages, :name, :category, :description
diff --git a/app/views/packages/updated.html.erb b/app/views/packages/updated.html.erb
new file mode 100644
index 0000000..b774c58
--- /dev/null
+++ b/app/views/packages/updated.html.erb
@@ -0,0 +1,23 @@
+<ol class="breadcrumb">
+ <li><a href="/">Home</a></li>
+ <li><%= link_to t(:packages), categories_path %></li>
+ <li class="active"><%= t :updated_packages %></li>
+</ol>
+
+<h1>
+ <%= t :updated_packages %>
+ <%= feed_icon updated_packages_path(format: :atom) %>
+</h1>
+
+<% cache("updated-full-#{@changes.hash}") do %>
+ <ul class="list-group">
+ <% @changes.each do |change|
+ _package = Package.find_by(:atom, cp_to_atom(change.category, change.package)) %>
+ <%= render partial: 'changed_package', object: change, as: 'change', locals: { package: _package, version: _package.version(change.version) } %>
+ <% end %>
+ </ul>
+<% end %>
+
+<% content_for :head do %>
+ <%= alternate_feed_link(updated_packages_url(format: :atom), t(:atom_feed)) %>
+<% end %>
diff --git a/app/views/useflags/_useflag_header.html.erb b/app/views/useflags/_useflag_header.html.erb
new file mode 100644
index 0000000..9422fac
--- /dev/null
+++ b/app/views/useflags/_useflag_header.html.erb
@@ -0,0 +1,5 @@
+<ol class="breadcrumb">
+ <li><a href="/"><%= t :home %></a></li>
+ <li><%= link_to t(:use_flags), useflags_path %></li>
+ <li class="active"><%= params[:id] %></li>
+</ol>
diff --git a/app/views/useflags/_useflag_result_row.html.erb b/app/views/useflags/_useflag_result_row.html.erb
new file mode 100644
index 0000000..084669f
--- /dev/null
+++ b/app/views/useflags/_useflag_result_row.html.erb
@@ -0,0 +1,4 @@
+<a class="list-group-item" href="<%= slf useflag_path useflag[:name] %>">
+ <h3 class="kk-search-result-header"><%= useflag[:name] %></h3>
+ <%= useflag[:description] %>
+</a>
diff --git a/app/views/useflags/index.html.erb b/app/views/useflags/index.html.erb
new file mode 100644
index 0000000..ac006f2
--- /dev/null
+++ b/app/views/useflags/index.html.erb
@@ -0,0 +1,45 @@
+<ol class="breadcrumb">
+ <li><a href="/"><%= t :home %></a></li>
+ <li class="active"><%= t :use_flags %></li>
+</ol>
+
+<h1><%= t :use_flags %></h1>
+
+<div class="alert alert-info">
+ Looking for the full USE flag index?
+ You can find it on our <a href="https://www.gentoo.org/support/use-flags/" class="alert-link">main website</a>.
+</div>
+
+<form action="<%= search_useflags_path %>" method="get" class="useflag-search">
+ <div class="typeahead-container">
+ <div class="typeahead-field">
+ <span class="typeahead-query">
+ <input id="q" name="q" type="search" autocomplete="off" placeholder="Find USE flags">
+ </span>
+ <span class="typeahead-button">
+ <button type="submit">
+ <span class="typeahead-search-icon"></span>
+ </button>
+ </span>
+ </div>
+ </div>
+</form>
+
+<br>
+
+<div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">Most widely used USE flags</h3>
+ </div>
+ <noscript>
+ <div class="panel-body kk-panel-content-sorry">
+ This feature requires JavaScript to work.
+ </div>
+ </noscript>
+ <div class="panel-body kk-useflag-bubble-container" id="bubble-placeholder" style="display: none;">
+ </div>
+</div>
+
+<%= javascript_include_tag 'useflags/typeahead.js' %>
+<%= javascript_include_tag 'd3.min.js' %>
+<%= javascript_include_tag 'useflags/render-bubbles.js' %>
diff --git a/app/views/useflags/popular.json.jbuilder b/app/views/useflags/popular.json.jbuilder
new file mode 100644
index 0000000..d174c9e
--- /dev/null
+++ b/app/views/useflags/popular.json.jbuilder
@@ -0,0 +1,10 @@
+json.name 'flags'
+json.children @popular_useflags do |flag|
+ # Very cheap filter for USE_EXPAND flags
+ next if flag['key'].include? '_'
+ next if flag['key'] =~ /^(doc|test|debug)$/
+
+ json.name flag['key']
+ json.size flag['doc_count']
+ json.children nil
+end
diff --git a/app/views/useflags/search.html.erb b/app/views/useflags/search.html.erb
new file mode 100644
index 0000000..061405b
--- /dev/null
+++ b/app/views/useflags/search.html.erb
@@ -0,0 +1,16 @@
+<ol class="breadcrumb">
+ <li><a href="/"><%= t :home %></a></li>
+ <li><%= link_to t(:use_flags), useflags_path %></li>
+ <li class="active">Search</li>
+</ol>
+
+<h1>USE Flag Search Results <small>for <%= params[:q] %></small></h1>
+
+<div class="panel panel-default">
+ <div class="panel-heading">
+ Results
+ </div>
+ <div class="list-group">
+ <%= render partial: 'useflag_result_row', collection: @flags, as: 'useflag' %>
+ </div>
+</div>
diff --git a/app/views/useflags/show.html.erb b/app/views/useflags/show.html.erb
new file mode 100644
index 0000000..b5b8bb6
--- /dev/null
+++ b/app/views/useflags/show.html.erb
@@ -0,0 +1,61 @@
+<%= render partial: 'useflag_header' %>
+
+<div class="row">
+ <div class="col-md-4">
+ <h1 class="stick-top">
+ <span class="fa fa-fw fa-sliders"></span>
+ <%= params[:id] %>
+ </h1>
+ </div>
+ <div class="col-md-8">
+ <% unless @useflags[:global].empty? %>
+ <div class="kk-useflag-group"><%= t :global_use_flag %></div>
+ <p class="lead" style="margin: 0;">
+ <%= @useflags[:global].first.description %>
+ </p>
+ <% else %>
+ <div class="kk-useflag-group"><%= t :local_use_flag %></div>
+ <% end %>
+ </div>
+</div>
+
+<hr>
+
+<% unless @useflags[:local].empty? %>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><%= t :local_use_package_list, flag: params[:id] %></h3>
+ </div>
+ <div class="table-responsive">
+ <table class="table">
+ <thead>
+ <th><%= t :package %></th>
+ <th><%= t :flag_description, flag: params[:id] %></th>
+ </thead>
+ <tbody>
+ <% @useflags[:local].keys.sort.each do |package| %>
+ <tr>
+ <th class="kk-nobreak-cell"><%= link_to package, slf(package_path(package)) %></th>
+ <td><%= annotate_useflag_description @useflags[:local][package].description %></td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+ </div>
+ </div>
+<% end %>
+
+<% unless @useflags[:global].empty? %>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><%= t :providing_packages_list, flag: params[:id], count: @packages.count %></h3>
+ </div>
+ <div class="panel-body">
+ <ul class="kk-col-list kk-3col-list kk-useflag-listing">
+ <% @packages.each do |package| %>
+ <li><%= link_to_package package['key'] %></li>
+ <% end %>
+ </ul>
+ </div>
+ </div>
+<% end %>
diff --git a/app/views/useflags/show_use_expand.html.erb b/app/views/useflags/show_use_expand.html.erb
new file mode 100644
index 0000000..45c2779
--- /dev/null
+++ b/app/views/useflags/show_use_expand.html.erb
@@ -0,0 +1,57 @@
+<%= render partial: 'useflag_header' %>
+
+<div class="row">
+ <div class="col-md-4">
+ <h1 class="stick-top">
+ <span class="fa fa-fw fa-sliders"></span>
+ <%= @useflag.strip_use_expand %>
+ </h1>
+ </div>
+ <div class="col-md-8">
+ <div class="kk-useflag-group"><%= t :named_use_expand_flag, name: @use_expand_flag_name %></div>
+ <p class="lead" style="margin: 0;">
+ <%= @useflags[:use_expand].first.description %>
+ </p>
+ </div>
+</div>
+
+<hr>
+
+<% unless @use_expand_flags.empty? %>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><%= t :other_use_expand_list, flag: @use_expand_flag_name %></h3>
+ </div>
+ <div class="table-responsive">
+ <table class="table">
+ <thead>
+ <th><%= t :use_flag %></th>
+ <th><%= t :description %></th>
+ </thead>
+ <tbody>
+ <% @use_expand_flags.each do |flag| %>
+ <tr>
+ <th class="kk-nobreak-cell"><%= link_to flag.name, slf(useflag_path(flag.name)) %></th>
+ <td><%= flag.description %></td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+ </div>
+ </div>
+<% end %>
+
+<% unless @packages.empty? %>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><%= t :providing_packages_list, flag: params[:id], count: @packages.count %></h3>
+ </div>
+ <div class="panel-body">
+ <ul class="kk-3col-list kk-useflag-listing">
+ <% @packages.each do |package| %>
+ <li><%= link_to_package package['key'] %></li>
+ <% end %>
+ </ul>
+ </div>
+ </div>
+<% end %>
diff --git a/app/views/useflags/suggest.json.jbuilder b/app/views/useflags/suggest.json.jbuilder
new file mode 100644
index 0000000..5e1571f
--- /dev/null
+++ b/app/views/useflags/suggest.json.jbuilder
@@ -0,0 +1 @@
+json.results @flags