From b145840323316610dd5a958cad89bbb84712ca5b Mon Sep 17 00:00:00 2001 From: Alex Legler Date: Wed, 20 Jul 2016 14:37:49 +0200 Subject: Initial commit w/currently running code --- app/assets/images/.keep | 0 app/assets/javascripts/application.js | 18 ++ app/assets/javascripts/arches.js | 2 + app/assets/javascripts/index/typeahead.js | 26 +++ app/assets/javascripts/kkuleomi.js | 9 + app/assets/javascripts/packages/show.js | 24 +++ app/assets/javascripts/useflags/render-bubbles.js | 70 +++++++ app/assets/javascripts/useflags/typeahead.js | 25 +++ app/assets/stylesheets/about.scss | 0 app/assets/stylesheets/application.css | 16 ++ app/assets/stylesheets/arches.scss | 3 + app/assets/stylesheets/categories.scss | 1 + app/assets/stylesheets/index.scss | 9 + app/assets/stylesheets/keywords.scss | 24 +++ app/assets/stylesheets/misc.scss | 158 ++++++++++++++ app/assets/stylesheets/packages.scss | 232 +++++++++++++++++++++ app/assets/stylesheets/sprockets-octicons.scss | 217 +++++++++++++++++++ app/assets/stylesheets/useflags.scss | 20 ++ app/controllers/about_controller.rb | 27 +++ app/controllers/application_controller.rb | 13 ++ app/controllers/arches_controller.rb | 63 ++++++ app/controllers/categories_controller.rb | 39 ++++ app/controllers/concerns/.keep | 0 app/controllers/concerns/package_update_feeds.rb | 35 ++++ app/controllers/index_controller.rb | 10 + app/controllers/packages_controller.rb | 84 ++++++++ app/controllers/useflags_controller.rb | 50 +++++ app/helpers/application_helper.rb | 51 +++++ app/helpers/arches_helper.rb | 2 + app/helpers/keywords_helper.rb | 90 ++++++++ app/helpers/links_helper.rb | 56 +++++ app/helpers/packages_helper.rb | 81 +++++++ app/helpers/portage_helper.rb | 8 + app/helpers/useflags_helper.rb | 5 + app/jobs/category_update_job.rb | 39 ++++ app/jobs/masks_update_job.rb | 7 + app/jobs/package_removal_job.rb | 16 ++ app/jobs/package_update_job.rb | 13 ++ app/jobs/record_change_job.rb | 30 +++ app/jobs/useflags_update_job.rb | 78 +++++++ app/mailers/.keep | 0 app/mailers/application_mailer.rb | 4 + app/mailers/feedback_mailer.rb | 8 + app/models/.keep | 0 app/models/category.rb | 39 ++++ app/models/change.rb | 13 ++ app/models/concerns/.keep | 0 app/models/package.rb | 74 +++++++ app/models/useflag.rb | 99 +++++++++ app/models/version.rb | 165 +++++++++++++++ app/views/about/changelog.html.md | 9 + app/views/about/feedback.html.erb | 64 ++++++ app/views/about/feeds.html.erb | 23 ++ app/views/about/help.html.erb | 11 + app/views/about/index.html.erb | 23 ++ app/views/about/legacy.atom.builder | 18 ++ app/views/arches/index.html.erb | 36 ++++ app/views/arches/keyworded.html.erb | 23 ++ app/views/arches/show.html.erb | 0 app/views/arches/stable.html.erb | 23 ++ app/views/categories/_category_header.html.erb | 21 ++ app/views/categories/_package_line.html.erb | 4 + app/views/categories/index.html.erb | 52 +++++ app/views/categories/index.json.jbuilder | 5 + app/views/categories/show.html.erb | 28 +++ app/views/categories/show.json.jbuilder | 10 + app/views/feedback_mailer/feedback_email.text.erb | 5 + app/views/feeds/changes.atom.builder | 60 ++++++ app/views/index/_package.html.erb | 8 + app/views/index/index.html.erb | 53 +++++ app/views/layouts/application.html.erb | 153 ++++++++++++++ app/views/layouts/mailer.html.erb | 5 + app/views/layouts/mailer.text.erb | 1 + app/views/packages/_changed_package.html.erb | 71 +++++++ app/views/packages/_changelog.html.erb | 19 ++ app/views/packages/_changelog_entry.html.erb | 31 +++ app/views/packages/_herd.html.erb | 1 + app/views/packages/_keyword_legend.html.erb | 17 ++ app/views/packages/_maintainer.html.erb | 1 + .../packages/_maintainer_needed_notice.html.erb | 7 + app/views/packages/_maintainer_spacer.html.erb | 1 + app/views/packages/_mask.html.erb | 30 +++ app/views/packages/_masks.html.erb | 10 + app/views/packages/_metadata.html.erb | 87 ++++++++ app/views/packages/_metadata_use.html.erb | 14 ++ app/views/packages/_package_header.html.erb | 31 +++ app/views/packages/_package_result_row.html.erb | 4 + app/views/packages/_removal_notice.html.erb | 5 + app/views/packages/_resources.html.erb | 35 ++++ app/views/packages/_useflag.html.erb | 5 + app/views/packages/_version_card.html.erb | 14 ++ app/views/packages/_version_row.html.erb | 12 ++ app/views/packages/_versions.html.erb | 38 ++++ app/views/packages/added.html.erb | 23 ++ app/views/packages/changelog.html.erb | 12 ++ app/views/packages/changelog.json.jbuilder | 1 + app/views/packages/keyworded.html.erb | 23 ++ app/views/packages/resolve.json.jbuilder | 4 + app/views/packages/search.html.erb | 38 ++++ app/views/packages/show.html.erb | 28 +++ app/views/packages/show.json.jbuilder | 43 ++++ app/views/packages/stable.html.erb | 23 ++ app/views/packages/suggest.json.jbuilder | 1 + app/views/packages/updated.html.erb | 23 ++ app/views/useflags/_useflag_header.html.erb | 5 + app/views/useflags/_useflag_result_row.html.erb | 4 + app/views/useflags/index.html.erb | 45 ++++ app/views/useflags/popular.json.jbuilder | 10 + app/views/useflags/search.html.erb | 16 ++ app/views/useflags/show.html.erb | 61 ++++++ app/views/useflags/show_use_expand.html.erb | 57 +++++ app/views/useflags/suggest.json.jbuilder | 1 + 112 files changed, 3539 insertions(+) create mode 100644 app/assets/images/.keep create mode 100644 app/assets/javascripts/application.js create mode 100644 app/assets/javascripts/arches.js create mode 100644 app/assets/javascripts/index/typeahead.js create mode 100644 app/assets/javascripts/kkuleomi.js create mode 100644 app/assets/javascripts/packages/show.js create mode 100644 app/assets/javascripts/useflags/render-bubbles.js create mode 100644 app/assets/javascripts/useflags/typeahead.js create mode 100644 app/assets/stylesheets/about.scss create mode 100644 app/assets/stylesheets/application.css create mode 100644 app/assets/stylesheets/arches.scss create mode 100644 app/assets/stylesheets/categories.scss create mode 100644 app/assets/stylesheets/index.scss create mode 100644 app/assets/stylesheets/keywords.scss create mode 100644 app/assets/stylesheets/misc.scss create mode 100644 app/assets/stylesheets/packages.scss create mode 100755 app/assets/stylesheets/sprockets-octicons.scss create mode 100644 app/assets/stylesheets/useflags.scss create mode 100644 app/controllers/about_controller.rb create mode 100644 app/controllers/application_controller.rb create mode 100644 app/controllers/arches_controller.rb create mode 100644 app/controllers/categories_controller.rb create mode 100644 app/controllers/concerns/.keep create mode 100644 app/controllers/concerns/package_update_feeds.rb create mode 100644 app/controllers/index_controller.rb create mode 100644 app/controllers/packages_controller.rb create mode 100644 app/controllers/useflags_controller.rb create mode 100644 app/helpers/application_helper.rb create mode 100644 app/helpers/arches_helper.rb create mode 100644 app/helpers/keywords_helper.rb create mode 100644 app/helpers/links_helper.rb create mode 100644 app/helpers/packages_helper.rb create mode 100644 app/helpers/portage_helper.rb create mode 100644 app/helpers/useflags_helper.rb create mode 100644 app/jobs/category_update_job.rb create mode 100644 app/jobs/masks_update_job.rb create mode 100644 app/jobs/package_removal_job.rb create mode 100644 app/jobs/package_update_job.rb create mode 100644 app/jobs/record_change_job.rb create mode 100644 app/jobs/useflags_update_job.rb create mode 100644 app/mailers/.keep create mode 100644 app/mailers/application_mailer.rb create mode 100644 app/mailers/feedback_mailer.rb create mode 100644 app/models/.keep create mode 100644 app/models/category.rb create mode 100644 app/models/change.rb create mode 100644 app/models/concerns/.keep create mode 100644 app/models/package.rb create mode 100644 app/models/useflag.rb create mode 100644 app/models/version.rb create mode 100644 app/views/about/changelog.html.md create mode 100644 app/views/about/feedback.html.erb create mode 100644 app/views/about/feeds.html.erb create mode 100644 app/views/about/help.html.erb create mode 100644 app/views/about/index.html.erb create mode 100644 app/views/about/legacy.atom.builder create mode 100644 app/views/arches/index.html.erb create mode 100644 app/views/arches/keyworded.html.erb create mode 100644 app/views/arches/show.html.erb create mode 100644 app/views/arches/stable.html.erb create mode 100644 app/views/categories/_category_header.html.erb create mode 100644 app/views/categories/_package_line.html.erb create mode 100644 app/views/categories/index.html.erb create mode 100644 app/views/categories/index.json.jbuilder create mode 100644 app/views/categories/show.html.erb create mode 100644 app/views/categories/show.json.jbuilder create mode 100644 app/views/feedback_mailer/feedback_email.text.erb create mode 100644 app/views/feeds/changes.atom.builder create mode 100644 app/views/index/_package.html.erb create mode 100644 app/views/index/index.html.erb create mode 100644 app/views/layouts/application.html.erb create mode 100644 app/views/layouts/mailer.html.erb create mode 100644 app/views/layouts/mailer.text.erb create mode 100644 app/views/packages/_changed_package.html.erb create mode 100644 app/views/packages/_changelog.html.erb create mode 100644 app/views/packages/_changelog_entry.html.erb create mode 100644 app/views/packages/_herd.html.erb create mode 100644 app/views/packages/_keyword_legend.html.erb create mode 100644 app/views/packages/_maintainer.html.erb create mode 100644 app/views/packages/_maintainer_needed_notice.html.erb create mode 100644 app/views/packages/_maintainer_spacer.html.erb create mode 100644 app/views/packages/_mask.html.erb create mode 100644 app/views/packages/_masks.html.erb create mode 100644 app/views/packages/_metadata.html.erb create mode 100644 app/views/packages/_metadata_use.html.erb create mode 100644 app/views/packages/_package_header.html.erb create mode 100644 app/views/packages/_package_result_row.html.erb create mode 100644 app/views/packages/_removal_notice.html.erb create mode 100644 app/views/packages/_resources.html.erb create mode 100644 app/views/packages/_useflag.html.erb create mode 100644 app/views/packages/_version_card.html.erb create mode 100644 app/views/packages/_version_row.html.erb create mode 100644 app/views/packages/_versions.html.erb create mode 100644 app/views/packages/added.html.erb create mode 100644 app/views/packages/changelog.html.erb create mode 100644 app/views/packages/changelog.json.jbuilder create mode 100644 app/views/packages/keyworded.html.erb create mode 100644 app/views/packages/resolve.json.jbuilder create mode 100644 app/views/packages/search.html.erb create mode 100644 app/views/packages/show.html.erb create mode 100644 app/views/packages/show.json.jbuilder create mode 100644 app/views/packages/stable.html.erb create mode 100644 app/views/packages/suggest.json.jbuilder create mode 100644 app/views/packages/updated.html.erb create mode 100644 app/views/useflags/_useflag_header.html.erb create mode 100644 app/views/useflags/_useflag_result_row.html.erb create mode 100644 app/views/useflags/index.html.erb create mode 100644 app/views/useflags/popular.json.jbuilder create mode 100644 app/views/useflags/search.html.erb create mode 100644 app/views/useflags/show.html.erb create mode 100644 app/views/useflags/show_use_expand.html.erb create mode 100644 app/views/useflags/suggest.json.jbuilder (limited to 'app') diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 0000000..e69de29 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: '{{category}}/{{name}} {{description}}' + } + }, + 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('

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: '{{name}} {{description}}' + } + }, + 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 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 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 = ' 📦 %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(' : %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=$~[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 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 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 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] 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 @@ + + +# <%= 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 @@ + + +

Feedback

+ +

+ Thanks for checking out the new packages.gentoo.org! +

+

+ This site is currently in an MVP 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. +

+

+ +
+
+
+
+

Send Feedback

+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+

Other ways to get in touch

+
+ +
+
+
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 @@ + + +

<%= t :update_feeds %>

+ +

+ You can find Atom feeds here: +

+ +
    +
  • + For all packages: Right column on the category listing. +
  • +
  • + For specific architectures: In the architectures section. +
  • +
  • + For specific packages: In the Resources box on the respective package pages. +
  • +
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 @@ + + +

<%= t :help %>

+ +

<%= t :keyword_table_legend %>

+ +<%= 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 @@ + + +

About packages.gentoo.org

+ +

Welcome to the new packages.gentoo.org!

+ +

+ This section will be extended with further information as the site continues to develop. + Feel free to get in touch if you have any questions that are not answered on this page. +

+ +

FAQ

+ +
+
How often is the site updated?
+
+ Updates are scheduled every 10 minutes and are processed using delayed jobs. + You can find the last time an import task was started in the footer. +
+
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 < +
  • Home
  • +
  • <%= t :architectures %>
  • + + +

    <%= t :architectures %>

    + +

    <%= t :arches_intro %>

    + +
    +
    +

    <%= t :architectures %>

    +
    +
    + + + + + + <% ::KKULEOMI_ARCHES.sort.each do |arch| %> + + + + + + <% end %> + +
    <%= arch %> + <%= link_to t(:keyworded_packages), keyworded_arch_path(id: arch) %> + <%= feed_icon keyworded_arch_path(id: arch, format: :atom) %> + + <%= link_to t(:stable_packages), stable_arch_path(id: arch) %> + <%= feed_icon stable_arch_path(id: arch, format: :atom) %> +
    +
    +
    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 @@ + + +

    + <%= t :keyworded_packages %> (<%= @arch %>) + <%= feed_icon keyworded_arch_path(id: @arch, format: :atom) %> +

    + +<% cache("keyworded-full-#{@arch}-#{@changes.hash}") do %> +
      + <% @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 %> +
    +<% 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 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 @@ + + +

    + <%= t :stable_packages %> (<%= @arch %>) + <%= feed_icon stable_arch_path(id: @arch, format: :atom) %> +

    + +<% cache("stable-full-#{@arch}-#{@changes.hash}") do %> +
      + <% @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 %> +
    +<% 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 @@ + + +
    +
    +

    + + <%= category.name %> +

    +
    +
    +

    + <%= category.description %> +

    +
    +
    + +
    \ 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 @@ + + <%= link_to package.name, slf(package_path(package.atom)) %> + <%= package.description %> + 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 @@ + + +

    <%= t :packages %>

    + +
    +
    +
    +
    +

    <%= t :browse_categories %>

    +
    +
    +
      + <%- prev_letter = 'z' -%> + <% @categories.each do |category| %> + <%- unless category.name[0].upcase == prev_letter ; prev_letter = category.name[0].upcase -%> +
    • <%= prev_letter %>
    • + <%- end -%> +
    • <%= link_to_category category %>
    • + <% end %> +
    +
    +
    +
    + +
    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' %> + +
    +
    + + +
    +
    +

    All packages

    +
    + + <%= render partial: 'package_line', collection: @packages, as: 'package' %> +
    +
    +
    +
    +
    +
    +

    Statistics

    +
    +
    + <%= @packages.count %> <%= t :packages %> +
    +
    +
    +
    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 @@ + + + + <%= change.category %>/<%= change.package %><%= "-#{change.version}" if change[:version] %> + + + <%= Package.find_by(:atom, cp_to_atom(change.category, change.package)).description %> + \ 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 @@ +
    +

    Welcome to the Home of <%= number_with_delimiter Package.count %> Gentoo Packages

    + +
    +
    +
    + + + + + + +
    +
    +
    +
    + +<% cache("added-#{@new_packages.hash}") do %> +
    +
    +

    + + <%= link_to t(:added_packages), added_packages_path %> +

    +
    +
    + + <%= render partial: 'package', collection: @new_packages, as: 'change' %> +
    +
    +
    +<% end %> + +<% cache("updated-#{@version_bumps.hash}") do %> +
    +
    +

    + + <%= link_to t(:updated_packages), updated_packages_path %> +

    +
    +
      + <% @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 %> +
    +
    +<% 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 @@ + + + + <%= "#{@title} – " if @title %>Gentoo Packages + + + + Gentoo Packages Database"> + + + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> + <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> + + + <% if content_for? :head -%> + <%= yield :head %> + <% end -%> + + +
    + + +
    + +
    +
    +
    + <%= yield %> +
    +
    +
    + +
    +
    +
    +
    +

    <%= t :app_name %>

    +
    +
    + <%= t :data_current_as_of %>
    <%= last_import_start ? i18n_date(last_import_start) : 'unknown' %> +
    +
    +
    +
    +
    +
    +
    +
    +

    Questions or comments?

    + Please feel free to contact us. +
    +
    +
    +
    + +
    +
    + © 2001–2016 Gentoo Foundation, Inc.
    + + Gentoo is a trademark of the Gentoo Foundation, Inc. + The contents of this document, unless otherwise expressly stated, are licensed under the + CC-BY-SA-3.0 license. + The Gentoo Name and Logo Usage Guidelines apply. + +
    +
    +
    +
    + + + + 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 @@ + + + <%= yield %> + + 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? %> +
  • +
    +
    +

    <%= link_to package.atom, slf(package_path(package.atom)) %>

    + + <%= package.description %> +
    + + <% unless change.arches == nil or change.arches.empty? %> + <%= t :added_keywords, keywords: change.arches.join(', ') %> + <% end %> + + <% unless (changelog_entry = matching_changelog_entry(change)).nil? %> +
    + + + + <%= changelog_entry[:message].lines.first %> + + +
    + <% end %> +
    +
    + + <%= i18n_date(change.created_at) %> + + <%= render partial: 'packages/version_card', object: version, as: 'version' %> +
    +
    +
  • +<% 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 @@ +
    +
    +

    Changelog

    +
    + +
    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 @@ +
  • + <%= annotate_bugs changelog[:message].lines.first %> +
    + + + + <% unless changelog[:files][:added].empty? %> + + + + + <% end %> + <% unless changelog[:files][:modified].empty? %> + + + + + <% end %> + <% unless changelog[:files][:deleted].empty? %> + + + + + <% end %> + +
    <%= safe_join(changelog[:files][:added].map {|f| link_to_gitweb_ebuild_diff(f, changelog[:id], @package.category, @package.name) }, ', ') %>
    <%= safe_join(changelog[:files][:modified].map {|f| link_to_gitweb_ebuild_diff(f, changelog[:id], @package.category, @package.name) }, ', ') %>
    <%= safe_join(changelog[:files][:deleted].map {|f| link_to_gitweb_ebuild_diff(f, changelog[:id], @package.category, @package.name) }, ', ') %>
    +
  • 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 @@ +
      +
    • + <%= keyword_icon_tag :stable %>  <%= t :legend_stable %> +
    • +
    • + <%= keyword_icon_tag :testing %>  <%= t :legend_testing %> +
    • +
    • + <%= keyword_icon_tag :unavailable %>  <%= t :legend_unavailable %> +
    • +
    • + <%= keyword_icon_tag :masked %>  <%= t :legend_masked %> +
    • +
    • +  <%= t :legend_unknown %> +
    • +
    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? %> +
    + This package needs a new maintainer!
    + If you are interested in helping with the maintenance of <%= package.name %>, please get in touch with our + Proxy Maintainers team. +
    +<% 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 @@ +
  • + <%= annotate_bugs(mask['reason']) %> + +
    + +
    + +
    + <%= mask['atoms'].join ', ' %> +
    +
    +
    + +
    + <%= mask['author'] %> (<%= mask['date'] %>) +
    +
    +
    +
  • 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? %> +
    +
    +

    <%= t :box_masks %>

    +
    +
      + <%= render partial: 'mask', collection: _masks.to_a, as: 'mask' %> +
    +
    +<% 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 @@ +
    +
    +

    <%= t :box_metadata %>

    +
    + +
    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? %> + <%= t :local_use_flags %> + <%= render partial: 'useflag', object: useflags['local'], as: 'useflags' %> +<% end %> +<% unless useflags['global'].empty? %> + <%= t :global_use_flags %> + <%= render partial: 'useflag', object: useflags['global'], as: 'useflags' %> +<% end %> +<% unless useflags['use_expand'].empty? %> + <% useflags['use_expand'].each_pair do |flag, values| %> + <%= t :use_expand_flag, flag: flag %> + <%= 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 @@ + + +
    +
    +

    + <%= package.category_model.name %>/ +
    + +
    <%= package.name %>
    +
    +

    +
    +
    +

    + <%= package.description %> +

    + +

    + <% if '' != package.homepage.first %> + <%= link_to package.homepage.first, package.homepage.first, rel: 'nofollow' %> + <% end %> +

    +
    +
    + +
    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 @@ + +

    <%= package.category %>/<%= package.name %>

    + <%= package.description %> +
    \ 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 @@ +
    + This package is masked and could be removed soon!
    + The mask comment indicates that this package is scheduled for removal from our package repository.
    + Please review the mask information below for more details. +
    \ 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 @@ + 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 @@ +
      +<% useflags.each_pair do |flag, flag_data| %> +
    • <%= link_to flag, useflag_path(id: flag_data['name']), :title => strip_tags(flag_data['description']), 'data-toggle' => 'tooltip' %>
    • +<% end %> +
    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 @@ +
    +

    <%= version.version %><%= version_slot version.slot %> <%= version_labels version %>

    +

    + <%= 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' %> +

    +
    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 @@ + + <%= version.version %><%= version_slot version.slot, version.subslot %> <%= version_labels version %> + <%= 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' %> + 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 @@ +
    +
    +

    + <%= t :box_versions %> + + + + + +

    +
    +
    + + + + + + + + + + + + + + + + + + <%= render partial: 'version_row', collection: versions, as: 'version' %> + +
    <%= t :version %>amd64x86alphaarmhppaia64ppcppc64sparc
    +
    +
    + + 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 @@ + + +

    + <%= t :added_packages %> + <%= feed_icon added_packages_path(format: :atom) %> +

    + +<% cache("added-full-#{@changes.hash}") do %> +
      + <% @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 %> +
    +<% 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? -%> +
  • + <%= t :changelog_empty %> +

    + + + <%= t :view_cvs_changelog %> + +
  • +<% 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 @@ + + +

    + <%= t :keyworded_packages %> + <%= feed_icon keyworded_packages_path(format: :atom) %> +

    + +<% cache("keyworded-full-#{@changes.hash}") do %> +
      + <% @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 %> +
    +<% 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 @@ +

    Search Results for <%= params[:q] %>

    + +<% if @packages.size > 0 %> +
    +
    + Results <%= @offset + 1 %>—<%= [@offset + Package.default_search_size, @packages.total].min %> of <%= @packages.total %> +
    +
    + <%= render partial: 'package_result_row', collection: @packages, as: 'package' %> +
    + +
    +<% else %> +
    +

    Nothing found. :( Try again?

    + +
    +
    +
    + + + + + + +
    +
    +
    +
    +<%= 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' %> + +
    +
    + <%= 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' %> +
    +
    + <%= render partial: 'resources', object: @package, as: 'package' %> +
    +
    + +<%= 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 @@ + + +

    + <%= t :stable_packages %> + <%= feed_icon stable_packages_path(format: :atom) %> +

    + +<% cache("stable-full-#{@changes.hash}") do %> +
      + <% @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 %> +
    +<% 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 @@ + + +

    + <%= t :updated_packages %> + <%= feed_icon updated_packages_path(format: :atom) %> +

    + +<% cache("updated-full-#{@changes.hash}") do %> +
      + <% @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 %> +
    +<% 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 @@ + 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 @@ + +

    <%= useflag[:name] %>

    + <%= useflag[:description] %> +
    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 @@ + + +

    <%= t :use_flags %>

    + +
    + Looking for the full USE flag index? + You can find it on our main website. +
    + + + +
    + +
    +
    +

    Most widely used USE flags

    +
    + + +
    + +<%= 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 @@ + + +

    USE Flag Search Results for <%= params[:q] %>

    + +
    +
    + Results +
    +
    + <%= render partial: 'useflag_result_row', collection: @flags, as: 'useflag' %> +
    +
    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' %> + +
    +
    +

    + + <%= params[:id] %> +

    +
    +
    + <% unless @useflags[:global].empty? %> +
    <%= t :global_use_flag %>
    +

    + <%= @useflags[:global].first.description %> +

    + <% else %> +
    <%= t :local_use_flag %>
    + <% end %> +
    +
    + +
    + +<% unless @useflags[:local].empty? %> +
    +
    +

    <%= t :local_use_package_list, flag: params[:id] %>

    +
    +
    + + + + + + + <% @useflags[:local].keys.sort.each do |package| %> + + + + + <% end %> + +
    <%= t :package %><%= t :flag_description, flag: params[:id] %>
    <%= link_to package, slf(package_path(package)) %><%= annotate_useflag_description @useflags[:local][package].description %>
    +
    +
    +<% end %> + +<% unless @useflags[:global].empty? %> +
    +
    +

    <%= t :providing_packages_list, flag: params[:id], count: @packages.count %>

    +
    +
    +
      + <% @packages.each do |package| %> +
    • <%= link_to_package package['key'] %>
    • + <% end %> +
    +
    +
    +<% 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' %> + +
    +
    +

    + + <%= @useflag.strip_use_expand %> +

    +
    +
    +
    <%= t :named_use_expand_flag, name: @use_expand_flag_name %>
    +

    + <%= @useflags[:use_expand].first.description %> +

    +
    +
    + +
    + +<% unless @use_expand_flags.empty? %> +
    +
    +

    <%= t :other_use_expand_list, flag: @use_expand_flag_name %>

    +
    +
    + + + + + + + <% @use_expand_flags.each do |flag| %> + + + + + <% end %> + +
    <%= t :use_flag %><%= t :description %>
    <%= link_to flag.name, slf(useflag_path(flag.name)) %><%= flag.description %>
    +
    +
    +<% end %> + +<% unless @packages.empty? %> +
    +
    +

    <%= t :providing_packages_list, flag: params[:id], count: @packages.count %>

    +
    +
    +
      + <% @packages.each do |package| %> +
    • <%= link_to_package package['key'] %>
    • + <% end %> +
    +
    +
    +<% 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 -- cgit v1.2.3-65-gdbad