{{n.title}}
-{{n.title}}
+++ + diff --git a/_includes/tutorial_list.html b/_includes/tutorial_list.html index a49f8713d4e49c..adf12c093e5ea2 100644 --- a/_includes/tutorial_list.html +++ b/_includes/tutorial_list.html @@ -40,12 +40,32 @@+ + Quiz: {{ quiz.title }} ++ + +{% for question in quiz.questions %} + {% assign qnum = forloop.index %} + +{% endfor %} + ++ + +
{{ site.data['tool-meta'][tool_id_path].name }}: {{ site.data['tool-meta'][tool_id_path].desc }}
-{{ page.observed_tool_ids[0] }}+
{{ tool_id_path }}
Metadata
- Servers Offering this Tool
- UseGalaxy.eu ({{ page.observed_tool_ids[0][1] }})
- This is a guess, we are not currently checking if that is actually present.
-
- Observed Tool Versions
- Within GTN tutorials
-
+ Links
- {% for tool in page.observed_tool_ids %}
- - {{ tool[1] }}
- {% endfor %}
+ {% if site.data['tool-meta'][tool_id_path]['bio.tools'] %}
+ -
+
+ bio.tools: {{ site.data['tool-meta'][tool_id_path]['bio.tools_name'] }}
+
+
+ {% endif %}
+ {% if site.data['toolshed-revisions'][tool_id] %}
+ -
+ {% assign repo_info = site.data['toolshed-revisions'][tool_id] %}
+
+ Galaxy ToolShed Repository
+
+
+ {% endif %}
- {% if site.data['tool-meta'][tool_id_path].edam_operations %}
+ {% assign edam_op_count = site.data['tool-meta'][tool_id_path].edam_operations | size %}
+ {% if edam_op_count > 0 %}
EDAM Operations
{% for topic in site.data['tool-meta'][tool_id_path].edam_operations %}
-
+ {% assign edam_info = topic | edamify: site %}
- {{ topic }}
+ {{ edam_info['Preferred Label'] | default: topic }}
{% endfor %}
{% endif %}
- {% if site.data['tool-meta'][tool_id_path].edam_topics%}
+ {% assign edam_topic_count = site.data['tool-meta'][tool_id_path].edam_topics | size %}
+ {% if edam_topic_count > 0 %}
EDAM Topics
{% for topic in site.data['tool-meta'][tool_id_path].edam_topics %}
-
+ {% assign edam_info = topic | edamify: site %}
- {{ topic }}
+ {{ edam_info['Preferred Label'] | default: topic }}
{% endfor %}
{% endif %}
+
+
+ Observed Tool Versions
+ Within GTN tutorials
+
+
+ {% for tool in page.observed_tool_ids %}
+
+ {{ tool[1] }}
+ {% assign v_id = tool[0] %}
+ {% assign supported = site | tool_version_support: v_id %}
+ {% assign usegalaxy_exact = supported.exact | where: "usegalaxy", "true" %}
+ {% assign other_exact = supported.exact | where: "usegalaxy", "false" %}
+
+
+ {% for inst in usegalaxy_exact %}
+ -
+
+ {{ inst.name }} âď¸
+
+
+ {% endfor %}
+ {% for inst in other_exact %}
+ -
+
+ {{ inst.name }}
+
+
+ {% endfor %}
+
+
+ {% endfor %}
+
@@ -74,3 +117,21 @@ Relevant Tutorials
Metadata
-Servers Offering this Tool
- UseGalaxy.eu ({{ page.observed_tool_ids[0][1] }}) -This is a guess, we are not currently checking if that is actually present.
- -Observed Tool Versions
-Within GTN tutorials
- +Links
-
- {% for tool in page.observed_tool_ids %}
-
- {{ tool[1] }} - {% endfor %} + {% if site.data['tool-meta'][tool_id_path]['bio.tools'] %} +
- + + bio.tools: {{ site.data['tool-meta'][tool_id_path]['bio.tools_name'] }} + + + {% endif %} + {% if site.data['toolshed-revisions'][tool_id] %} +
- + {% assign repo_info = site.data['toolshed-revisions'][tool_id] %} + + Galaxy ToolShed Repository + + + {% endif %}
EDAM Operations
-
{% for topic in site.data['tool-meta'][tool_id_path].edam_operations %}
- + {% assign edam_info = topic | edamify: site %} - {{ topic }} + {{ edam_info['Preferred Label'] | default: topic }} {% endfor %}
EDAM Topics
-
{% for topic in site.data['tool-meta'][tool_id_path].edam_topics %}
- + {% assign edam_info = topic | edamify: site %} - {{ topic }} + {{ edam_info['Preferred Label'] | default: topic }} {% endfor %}
Observed Tool Versions
+Within GTN tutorials
+ + + {% for tool in page.observed_tool_ids %} +{{ tool[1] }}
+ {% assign v_id = tool[0] %} + {% assign supported = site | tool_version_support: v_id %} + {% assign usegalaxy_exact = supported.exact | where: "usegalaxy", "true" %} + {% assign other_exact = supported.exact | where: "usegalaxy", "false" %} + +-
+ {% for inst in usegalaxy_exact %}
+
- + + {{ inst.name }} âď¸ + + + {% endfor %} + {% for inst in other_exact %} +
- + + {{ inst.name }} + + + {% endfor %} +
Relevant Tutorials
{{ topic.title }} â Community Home
+ +This is a new, experimental "Community Home" for a given topic. It is intended to highlight community contributions over the years to a topic! + +{% assign prio = "news events learning-pathways tutorials slides recordings faqs workflows grants organisations contributors" | split:" " %} + + +{% assign topic_material_by_years = site | list_topic_materials_yearly:topic.name %} +{% assign cumulative_counts_by_years = site | count_topic_materials_yearly:topic.name %} + +{{ year[0] }} Year in Review
+So many new additions to our community!
+ +{{ p | regex_replace: '-', ' ' | titlecase }}
+-
+ {% for resource in group %}
+
- {{ resource[2].title }} + {% endfor %} +
Cumulative Data as CSV
++year,{% for point in cumulative_counts_by_years['tutorials'] %}{{ point.x | regex_replace:'-.*', '' }}{%unless forloop.last%},{%endunless%}{% endfor %} +{% for p in prio %}{{ p }},{% for point in cumulative_counts_by_years[p] %}{{ point.y }}{%unless forloop.last%},{%endunless%}{% endfor %} +{% endfor %} + ++ + + + + + + + + + diff --git a/_layouts/contributor_index.html b/_layouts/contributor_index.html index 205e2a3e207ae2..76f5f23fe57ba6 100644 --- a/_layouts/contributor_index.html +++ b/_layouts/contributor_index.html @@ -249,8 +249,8 @@
GitHub Activity
{% if page.gh_prs_count > 0 %}
{{ page.gh_prs_count }} Merged Pull Requests
- See all of the - {% icon github %} Pull Requests and + See all of the + {% icon github %} Pull Requests and {% icon github %} Commits by {{ entity.name | default: page.contributor }}.
-
@@ -350,15 +350,23 @@
- #{r} ) - end.join("\n") - out += '
- #{r} ) + end.join("\n") + out += '
External Links
{% endif %} {% if entity.funding_id %} - Grant ID: +Funding information
+ {% if entity.funder_name %}
+
Funding body: {{entity.funder_name}}
+ {% endif %}
+
Grant ID:
{% if entity.funding_database %}
{% assign url = entity | fetch_funding_url %}
{{ entity.funding_id }}
{% else %}
- {{ entity.funding_id }}
+
{{ entity.funding_id }}
{% endif %}
+
+
External Links
{% endif %} -Favourite Topics
+Favourite Topics
-Favourite Formats
+Favourite Formats
diff --git a/_layouts/event.html b/_layouts/event.html index c58d546822daac..f4d4af5d11ca05 100644 --- a/_layouts/event.html +++ b/_layouts/event.html @@ -173,13 +173,14 @@Get set up for the course!
Follow the steps below to get set up with everything you need to participate in the course!
- {% unless event.infrastructure.no_galaxy %} + {% if event.infrastructure.servers %}Create a Galaxy Account
@@ -200,7 +201,7 @@
Create a Galaxy Account
Contributor Hall of Fame
Quickstart
- diff --git a/_layouts/topic-maintainer.html b/_layouts/topic-maintainer.html index 19bb848e8a6110..2b8044cc06260c 100644 --- a/_layouts/topic-maintainer.html +++ b/_layouts/topic-maintainer.html @@ -6,7 +6,7 @@ {% assign topic_material = site | list_materials_structured:topic.name %} {% assign topic_material_flat = site | list_materials_flat:topic.name %} -{{ topic.title }} Editorial Board Home
+{{ topic.title }} â Editorial Board Home
This is a new, experimental "Editorial Board Home" for a given topic. It is intended to provide a single place for maintainers and editorial board members to find out key information about their topic and identify action items. @@ -249,7 +249,7 @@Events using materials from this Topic
TODO once this is merged: https://github.com/galaxyproject/training-material/pull/4963 -Statistics For Your Materials
+Statistics For Your Materials
{{ topic.title }}
{{ topic.summary | markdownify }}
- {% if topic.gitter %} -For any question or discussions related to this topic, or to connect with others in the community, please visit the community chat:
-{% icon comment %} Community Matrix Chat
- {% endif%} - - - - {% if topic.requirements %} -Requirements
-Before diving into this topic, we recommend you to have a look at:
- --
- {% include _includes/display_extra_training.md extra_trainings=topic.requirements %}
-
For any question or discussions related to this topic, or to connect with others in the community, please visit the community chat:
+{% icon comment %} Community Matrix Chat
+ {% endif%} + + {% if topic.requirements %} +Requirements
+Before diving into this topic, we recommend you to have a look at:
+-
+ {% include _includes/display_extra_training.md extra_trainings=topic.requirements %}
+
Not sure where to start?
Not sure where to start?
++ Try a {{ topic.title }} Learning Pathway! + + {% for cta in topic.learning_path_ctas %} + {{ cta[0] }} + {% endfor %} + +
+Material
{% if topic.toc %} @@ -85,6 +112,12 @@Frequently Asked Questions
{% endif %} {% endunless %} + {% unless topic.tag_based %} +Community Resources
+ Community Home + Maintainer Home + {% endunless %} + {% if topic.editorial_board %}Editorial Board
This material is reviewed by our Editorial Board:
diff --git a/_layouts/tutorial_hands_on.html b/_layouts/tutorial_hands_on.html index 441ab982b9298b..e06c83835a9a57 100644 --- a/_layouts/tutorial_hands_on.html +++ b/_layouts/tutorial_hands_on.html @@ -322,6 +322,7 @@{{ locale['faqs'] | default: "Frequently Asked Questions" }}
If not, please ask your question on the GTN Gitter Channel or the Galaxy Help Forum + {% if topic.references %}Useful literature
diff --git a/_plugins/abbr.rb b/_plugins/abbr.rb index 5d09dd661daee4..e4b96e7d682861 100644 --- a/_plugins/abbr.rb +++ b/_plugins/abbr.rb @@ -3,61 +3,63 @@ require 'jekyll' module Jekyll - ## - # This class modifies the page contents to replace {abbr} with the associated - # abbreviation in a proper tag. It's done as a generator because it's - # easier to operate once per page and be able to easily see if we've generated - # the abbreviation before. - # - # This could in theory operate as a simple tag, but I'm not sure how to keep - # track of "# of times seen per page" there. - class Abbreviate < Jekyll::Generator - safe true - - def initialize(config) # :nodoc: - super - @config = config['abbreviate'] ||= {} - end + module Generators + ## + # This class modifies the page contents to replace {abbr} with the associated + # abbreviation in a proper tag. It's done as a generator because it's + # easier to operate once per page and be able to easily see if we've generated + # the abbreviation before. + # + # This could in theory operate as a simple tag, but I'm not sure how to keep + # track of "# of times seen per page" there. + class Abbreviate < Jekyll::Generator + safe true - def generate(site) # :nodoc: - site.pages - .reject { |page| skip_layout?(page.data['layout']) } - .each { |page| abbreviate page } - site.posts.docs - .reject { |post| skip_layout?(post.data['layout']) } - .each { |post| abbreviate post } - end + def initialize(config) # :nodoc: + super + @config = config['abbreviate'] ||= {} + end + + def generate(site) # :nodoc: + site.pages + .reject { |page| skip_layout?(page.data['layout']) } + .each { |page| abbreviate page } + site.posts.docs + .reject { |post| skip_layout?(post.data['layout']) } + .each { |post| abbreviate post } + end - private + private - def abbreviate(page) # :nodoc: - return unless page.data.key?('abbreviations') + def abbreviate(page) # :nodoc: + return unless page.data.key?('abbreviations') - seen = {} - page.data['abbreviations'].each do |abbr, definition| - page.content = page.content.gsub(/\{(#{abbr})\}/) do - if seen.key?(abbr) - firstdef = false - else - firstdef = true - seen[abbr] = true - end + seen = {} + page.data['abbreviations'].each do |abbr, definition| + page.content = page.content.gsub(/\{(#{abbr})\}/) do + if seen.key?(abbr) + firstdef = false + else + firstdef = true + seen[abbr] = true + end - if firstdef - "#{definition} (#{abbr})" - else - "#{abbr}" + if firstdef + "#{definition} (#{abbr})" + else + "#{abbr}" + end end end end - end - def skip_layout?(layout) - to_skip = @config['skip_layouts'] || [] + def skip_layout?(layout) + to_skip = @config['skip_layouts'] || [] - true if to_skip.empty? + true if to_skip.empty? - to_skip.include?(layout) + to_skip.include?(layout) + end end end end diff --git a/_plugins/api.rb b/_plugins/api.rb index 263701d4d1df18..597b5a066c2372 100644 --- a/_plugins/api.rb +++ b/_plugins/api.rb @@ -6,8 +6,10 @@ require './_plugins/gtn/metrics' require './_plugins/gtn/scholar' require './_plugins/gtn/git' +require './_plugins/gtn/hooks' require './_plugins/gtn/ro-crate' require './_plugins/gtn' +require './_plugins/util' ## # Use Jekyll's Markdown converter to convert text to HTML @@ -62,123 +64,85 @@ def mapContributor(site, c) end module Jekyll - ## - # This class generates the GTN's "api" by writing out a folder full of JSON files. - class APIGenerator < Generator - + module Generators ## - # Generates /api/configuration.json - # Params: - # +site+:: +Jekyll::Site+ object - # Returns: - # nil - def generateConfiguration(site) - page2 = PageWithoutAFile.new(site, '', 'api/', 'configuration.json') - site.config.update(Gtn::Git.discover) - # Remove every key that starts with "cached_" - conf = site.config.reject { |k, _v| k.to_s.start_with?('cached_') } - page2.content = JSON.pretty_generate(conf) - page2.data['layout'] = nil - site.pages << page2 - end + # This class generates the GTN's "api" by writing out a folder full of JSON files. + class APIGenerator + + def copy(site, source, dest) + # It isn't unusual that some of these might not exist in dev envs. + if File.exist?(site.in_source_dir(source)) + if ! Dir.exist?(File.dirname(site.in_dest_dir(dest))) + FileUtils.mkdir_p(File.dirname(site.in_dest_dir(dest))) + end - ## - # Generates /api/version.json - # Params: - # +site+:: +Jekyll::Site+ object - # Returns: - # nil - def generateVersion(site) - page2 = PageWithoutAFile.new(site, '', 'api/', 'version.json') - page2.content = JSON.pretty_generate(Gtn::Git.discover) - page2.data['layout'] = nil - site.pages << page2 - end + FileUtils.cp(site.in_source_dir(source), site.in_dest_dir(dest)) + end + end - ## - # Generates /api/data-library.yaml - # Params: - # +site+:: +Jekyll::Site+ object - # Returns: - # nil - def generateLibrary(site) - Jekyll.logger.info '[GTN/API] Data Library' - page2 = PageWithoutAFile.new(site, '', 'api/', 'data-library.yaml') - data_libraries = Dir.glob('topics/**/data-library.yaml') - data_libraries.map! { |x| YAML.load_file(x) } - page2.content = JSON.pretty_generate(Gtn::Git.discover) - page2.data['layout'] = nil - site.pages << page2 - end + def write(site, dest, data, json: true, pretty: true) + # Since we're doing this ourselves, need to be responsible for ensuring + # the directory exists. + if ! Dir.exist?(File.dirname(site.in_dest_dir(dest))) + FileUtils.mkdir_p(File.dirname(site.in_dest_dir(dest))) + end - ## - # Runs the generation process - # Params: - # +site+:: +Jekyll::Site+ object - def generate(site) - generateConfiguration(site) - # For some reason the templating isn't working right here. - # generateVersion(site) - # TODO: - # generateLibrary(site) - - Jekyll.logger.info '[GTN/API] Generating API' - # Full Bibliography - Gtn::Scholar.load_bib(site) - Jekyll.logger.debug '[GTN/API] Bibliography' - page3 = PageWithoutAFile.new(site, '', 'api/', 'gtn.bib') - page3.content = site.config['cached_global_bib'].to_s - page3.data['layout'] = nil - site.pages << page3 - - # Metrics endpoint, /metrics - page2 = PageWithoutAFile.new(site, '', '', 'metrics') - page2.content = "{% raw %}\n#{Gtn::Metrics.generate_metrics(site)}{% endraw %}" - page2.data['layout'] = nil - site.pages << page2 - - # Public tool listing - page2 = PageWithoutAFile.new(site, '', 'api/', 'psl.json') - page2.content = JSON.generate(site.data['public-server-tools']) - page2.data['layout'] = nil - site.pages << page2 - - # Tool Categories - page2 = PageWithoutAFile.new(site, '', 'api/', 'toolcats.json') - page2.content = JSON.generate(site.data['toolcats']) - page2.data['layout'] = nil - site.pages << page2 - - # Tool Categories - page2 = PageWithoutAFile.new(site, '', 'api/', 'toolshed-revisions.json') - page2.content = JSON.generate(site.data['toolshed-revisions']) - page2.data['layout'] = nil - site.pages << page2 - - # Feedback Data - page2 = PageWithoutAFile.new(site, '', 'api/', 'feedback2.json') - page2.content = JSON.generate(site.data['feedback2']) - page2.data['layout'] = nil - site.pages << page2 - - # Contributors - Jekyll.logger.debug '[GTN/API] Contributors, Funders, Organisations' - %w[contributors grants organisations].each do |type| - page2 = PageWithoutAFile.new(site, '', 'api/', "#{type}.json") - page2.content = JSON.pretty_generate(site.data[type].map { |c, _| mapContributor(site, c) }) - page2.data['layout'] = nil - site.pages << page2 - site.data['contributors'].each do |c, _| - page4 = PageWithoutAFile.new(site, '', 'api/', "#{type}s/#{c}.json") - page4.content = JSON.pretty_generate(mapContributor(site, c)) - page4.data['layout'] = nil - site.pages << page4 + if json + if pretty + File.write(site.in_dest_dir(dest), JSON.pretty_generate(data)) + else + File.write(site.in_dest_dir(dest), JSON.generate(data)) + end + else + # Pretty isn't relevant. + File.write(site.in_dest_dir(dest), data) end end - page2 = PageWithoutAFile.new(site, '', 'api/', 'contributors.geojson') - page2.content = JSON.pretty_generate( - { + ## + # Runs the generation process + # Params: + # +site+:: +Jekyll::Site+ object + def generate(site) + Jekyll.logger.info '[GTN/API] Generating API' + + write(site, 'api/configuration.json', site.config.reject { |k, _v| k.to_s.start_with?('cached_') }) + write(site, 'api/swagger.json', site.data['swagger']) + write(site, 'api/version.json', Gtn::Git.discover) + + # Full Bibliography + Jekyll.logger.debug '[GTN/API] Bibliography' + write(site, 'api/gtn.bib', site.config['cached_global_bib'].to_s, json: false) + + # Metrics endpoint, /metrics + write(site, 'api/metrics', Gtn::Metrics.generate_metrics(site), json: false) + + # Public tool listing + write(site, 'api/psl.json', site.data['public-server-tools'], pretty: false) + + # Tool Categories + write(site, 'api/toolcats.json', site.data['toolcats'], pretty: false) + + # Tool Categories + write(site, 'api/toolshed-revisions.json', site.data['toolshed-revisions'], pretty: false) + + # Feedback Data + write(site, 'api/feedback2.json', site.data['feedback2'], pretty: false) + copy(site, 'metadata/feedback.csv', 'api/feedback.csv') + copy(site, 'metadata/feedback2.yaml', 'api/feedback2.yaml') + + # Contributors + Jekyll.logger.debug '[GTN/API] Contributors, Funders, Organisations' + %w[contributors grants organisations].each do |type| + pfo = site.data[type].map { |c, _| mapContributor(site, c) } + write(site, "api/#{type}.json", pfo, pretty: false) + + site.data['contributors'].each do |c, _| + write(site, "api/#{type}/#{c}.json", mapContributor(site, c)) + end + end + + geojson = { 'type' => 'FeatureCollection', 'features' => site.data['contributors'] .select { |_k, v| v.key? 'location' } @@ -197,161 +161,136 @@ def generate(site) } end } - ) - page2.data['layout'] = nil - site.pages << page2 - - # Trigger the topic cache to generate if it hasn't already - Jekyll.logger.debug '[GTN/API] Tutorials' - TopicFilter.topic_filter(site, 'does-not-matter') - TopicFilter.list_topics(site).map do |topic| - out = site.data[topic].dup - out['materials'] = TopicFilter.topic_filter(site, topic).map do |x| - q = x.dup - q['contributors'] = Gtn::Contributors.get_contributors(q).dup.map do |c| - mapContributor(site, c) - end + write(site, "api/contributors.geojson", geojson) + + + # Trigger the topic cache to generate if it hasn't already + Jekyll.logger.debug '[GTN/API] Tutorials' + Gtn::TopicFilter.topic_filter(site, 'does-not-matter') + Gtn::TopicFilter.list_topics(site).map do |topic| + out = site.data[topic].dup + out['materials'] = Gtn::TopicFilter.topic_filter(site, topic).map do |x| + q = x.dup + q['contributors'] = Gtn::Contributors.get_contributors(q).dup.map do |c| + mapContributor(site, c) + end - q['urls'] = {} + q['urls'] = {} - if !q['hands_on'].nil? - q['urls']['hands_on'] = site.config['url'] + site.config['baseurl'] + "/api/topics/#{q['url'][8..-6]}.json" - end + if !q['hands_on'].nil? + q['urls']['hands_on'] = site.config['url'] + site.config['baseurl'] + "/api/topics/#{q['url'][8..-6]}.json" + end + + if !q['slides'].nil? + q['urls']['slides'] = site.config['url'] + site.config['baseurl'] + "/api/topics/#{q['url'][8..-6]}.json" + end + + # Write out the individual page + # Delete the ref to avoid including it by accident + q.delete('ref') + q.delete('ref_tutorials') + q.delete('ref_slides') + write(site, "api/topics/#{q['url'][7..-6]}.json", q) - if !q['slides'].nil? - q['urls']['slides'] = site.config['url'] + site.config['baseurl'] + "/api/topics/#{q['url'][8..-6]}.json" + q + end + out['editorial_board'] = out['editorial_board'].map do |c| + mapContributor(site, c) end - # Write out the individual page - page6 = PageWithoutAFile.new(site, '', 'api/topics/', "#{q['url'][7..-6]}.json") - # Delete the ref to avoid including it by accident - q.delete('ref') - q.delete('ref_tutorials') - q.delete('ref_slides') - page6.content = JSON.pretty_generate(q) - page6.data['layout'] = nil - site.pages << page6 - - q - end - out['editorial_board'] = out['editorial_board'].map do |c| - mapContributor(site, c) + write(site, "api/topics/#{topic}.json", out) end - page2 = PageWithoutAFile.new(site, '', 'api/topics/', - "#{topic}.json") - page2.content = JSON.pretty_generate(out) - page2.data['layout'] = nil - site.pages << page2 - end + topics = {} + Jekyll.logger.debug '[GTN/API] Topics' + # Individual Topic Indexes + site.data.each_pair do |k, v| + if v.is_a?(Hash) && v.key?('type') && v.key?('editorial_board') + + topics[k] = { + 'name' => v['name'], + 'title' => v['title'], + 'summary' => v['summary'], + 'url' => site.config['url'] + site.config['baseurl'] + "/api/topics/#{k}.json", + 'editorial_board' => v['editorial_board'].map { |c| mapContributor(site, c) } + } + end + end - topics = {} - Jekyll.logger.debug '[GTN/API] Topics' - # Individual Topic Indexes - site.data.each_pair do |k, v| - if v.is_a?(Hash) && v.key?('type') && v.key?('editorial_board') - - topics[k] = { - 'name' => v['name'], - 'title' => v['title'], - 'summary' => v['summary'], - 'url' => site.config['url'] + site.config['baseurl'] + "/api/topics/#{k}.json", - 'editorial_board' => v['editorial_board'].map { |c| mapContributor(site, c) } + # Videos.json + # { + # "id": "transcriptomics/tutorials/mirna-target-finder/slides", + # "topic": "Transcriptomics", + # "title": "Whole transcriptome analysis of Arabidopsis thaliana" + # }, + + videos = Gtn::TopicFilter.list_videos(site).map do |m| + { + id: "#{m['topic_name']}/tutorials/#{m['tutorial_name']}/slides", + topic: m['topic_name_human'], + title: m['title'] } end - end + write(site, "api/videos.json", videos) - # Videos.json - # { - # "id": "transcriptomics/tutorials/mirna-target-finder/slides", - # "topic": "Transcriptomics", - # "title": "Whole transcriptome analysis of Arabidopsis thaliana" - # }, - - page2 = PageWithoutAFile.new(site, '', 'api/', 'videos.json') - page2.content = JSON.pretty_generate(TopicFilter.list_videos(site).map do |m| - { - id: "#{m['topic_name']}/tutorials/#{m['tutorial_name']}/slides", - topic: m['topic_name_human'], - title: m['title'] - } - end) - page2.data['layout'] = nil - site.pages << page2 - - # Overall topic index - page2 = PageWithoutAFile.new(site, '', 'api/', 'topics.json') - page2.content = JSON.pretty_generate(topics) - page2.data['layout'] = nil - site.pages << page2 - - Jekyll.logger.debug '[GTN/API] Tutorial and Slide pages' - - # Deploy the feedback file as well - page2 = PageWithoutAFile.new(site, '', 'api/', 'feedback.json') - page2.content = JSON.pretty_generate(site.data['feedback']) - page2.data['layout'] = nil - site.pages << page2 - - # Top Tools - Jekyll.logger.debug '[GTN/API] Top Tools' - page2 = PageWithoutAFile.new(site, '', 'api/', 'top-tools.json') - page2.content = JSON.pretty_generate(TopicFilter.list_materials_by_tool(site)) - page2.data['layout'] = nil - site.pages << page2 - - # Not really an API - TopicFilter.list_materials_by_tool(site).each do |tool, tutorials| - page2 = PageWithoutAFile.new(site, '', 'by-tool/', "#{tool.gsub('%20', ' ')}.html") - page2.content = nil - page2.data['layout'] = 'by_tool' - page2.data['short_tool'] = tool - page2.data['observed_tool_ids'] = tutorials['tool_id'] - page2.data['tutorial_list'] = tutorials['tutorials'] - site.pages << page2 - end - # GA4GH TRS Endpoint - # Please note that this is all a fun hack - TopicFilter.list_all_materials(site).select { |m| m['workflows'] }.each do |material| - material['workflows'].each do |workflow| - wfid = workflow['wfid'] - wfname = workflow['wfname'] + # Overall topic index + write(site, "api/topics.json", topics) - page2 = PageWithoutAFile.new(site, '', "api/ga4gh/trs/v2/tools/#{wfid}/versions/", "#{wfname}.json") - page2.content = JSON.pretty_generate( - { + Jekyll.logger.debug '[GTN/API] Tutorial and Slide pages' + + # Deploy the feedback file as well + write(site, "api/feedback.json", site.data['feedback']) + + # Top Tools + Jekyll.logger.debug '[GTN/API] Top Tools' + write(site, "api/top-tools.json", Gtn::TopicFilter.list_materials_by_tool(site)) + + # GA4GH TRS Endpoint + # Please note that this is all a fun hack + Gtn::TopicFilter.list_all_materials(site).select { |m| m['workflows'] }.each do |material| + material['workflows'].each do |workflow| + wfid = workflow['wfid'] + wfname = workflow['wfname'] + + ga4gh_blob = { 'id' => wfname, 'url' => site.config['url'] + site.config['baseurl'] + material['url'], 'name' => 'v1', 'author' => [], 'descriptor_type' => ['GALAXY'], } - ) - page2.data['layout'] = nil - site.pages << page2 - - page2 = PageWithoutAFile.new(site, '', "api/ga4gh/trs/v2/tools/#{wfid}/versions/#{wfname}/GALAXY", - 'descriptor.json') - page2.content = JSON.pretty_generate( - { + write(site, "api/ga4gh/trs/v2/tools/#{wfid}/versions/#{wfname}.json", ga4gh_blob) + + + + descriptor = { 'content' => File.read("#{material['dir']}/workflows/#{workflow['workflow']}"), 'checksum' => [], 'url' => nil, } - ) - page2.data['layout'] = nil - site.pages << page2 + write(site, "api/ga4gh/trs/v2/tools/#{wfid}/versions/#{wfname}/GALAXY/descriptor.json", descriptor) + end end end end end end + +Jekyll::Hooks.register :site, :post_read do |site| + if Jekyll.env == 'production' + Gtn::Hooks.by_tool(site) + end +end + # Basically like `PageWithoutAFile`, we just write out the ones we'd created earlier. Jekyll::Hooks.register :site, :post_write do |site| # No need to run this except in prod. if Jekyll.env == 'production' + # Build our API + api = Jekyll::Generators::APIGenerator.new + api.generate(site) # Public tool listing: reorganised if site.data['public-server-tools'] && site.data['public-server-tools']['tools'] @@ -373,7 +312,7 @@ def generate(site) Jekyll.logger.debug '[GTN/API/PSL] PSL Dataset not available, are you in a CI environment?' end - TopicFilter.list_all_materials(site).each do |material| + Gtn::TopicFilter.list_all_materials(site).each do |material| directory = material['dir'] if material['slides'] @@ -417,7 +356,7 @@ def generate(site) # ro-crate-metadata.json crate_start = Time.now count = 0 - TopicFilter.list_all_materials(site).select { |m| m['workflows'] }.each do |material| + Gtn::TopicFilter.list_all_materials(site).select { |m| m['workflows'] }.each do |material| material['workflows'].each do |workflow| Gtn::RoCrate.write(site, dir, material, workflow, site.config['url'], site.config['baseurl']) count += 1 diff --git a/_plugins/author-page.rb b/_plugins/author-page.rb index a82bccdfcff80e..1666a93baf847a 100644 --- a/_plugins/author-page.rb +++ b/_plugins/author-page.rb @@ -4,167 +4,169 @@ require './_plugins/gtn' module Jekyll - ## - # This class generates the GTN's author pags - class AuthorPageGenerator < Generator - safe true - + module Generators ## - # This extracts the contributions and pushes them on to an existing - # datastructure, modifying it in the process. It's pretty gross. - # - # Params - # +t+:: The tutorial, slide, or news item - # +datastructure+:: The hash of contributors that the author information should be pushed onto - # +flat+:: Whether the datastructure is a flat array or a nested array - # - # Returns - # +datastructure+:: The modified datastructure - def pusher(t, datastructure, flat) - if t.data.key?('contributors') - if flat - t.data['contributors'].each { |c| datastructure[c].push(t) } - else - t.data['contributors'].each { |c| datastructure[c].push([t, nil]) } - end - elsif t.data.key?('contributions') - t.data['contributions'].each do |contribution_type, contributor| - contributor.each do |c| - if flat - datastructure[c].push(t) - else - datastructure[c].push([t, contribution_type]) + # This class generates the GTN's author pags + class AuthorPageGenerator < Generator + safe true + + ## + # This extracts the contributions and pushes them on to an existing + # datastructure, modifying it in the process. It's pretty gross. + # + # Params + # +t+:: The tutorial, slide, or news item + # +datastructure+:: The hash of contributors that the author information should be pushed onto + # +flat+:: Whether the datastructure is a flat array or a nested array + # + # Returns + # +datastructure+:: The modified datastructure + def pusher(t, datastructure, flat) + if t.data.key?('contributors') + if flat + t.data['contributors'].each { |c| datastructure[c].push(t) } + else + t.data['contributors'].each { |c| datastructure[c].push([t, nil]) } + end + elsif t.data.key?('contributions') + t.data['contributions'].each do |contribution_type, contributor| + contributor.each do |c| + if flat + datastructure[c].push(t) + else + datastructure[c].push([t, contribution_type]) + end end end end - end - t.data['maintainers'].each { |c| datastructure[c].push([t, 'maintainer']) } if t.data.key?('maintainers') - t.data['funding'].each { |c| datastructure[c].push([t, 'funding']) } if t.data.key?('funding') + t.data['maintainers'].each { |c| datastructure[c].push([t, 'maintainer']) } if t.data.key?('maintainers') + t.data['funding'].each { |c| datastructure[c].push([t, 'funding']) } if t.data.key?('funding') - datastructure - end - - ## - # This generates the author pages - # Params - # +site+:: The site object - def generate(site) - return unless site.layouts.key? 'contributor_index' - - dir = 'hall-of-fame' - - # pre-calculating this hash saves about 4.9 seconds off the previous - # build time of 5 seconds. - tutorials_by_author = Hash.new { |hash, key| hash[key] = [] } - learning_pathways_by_author = Hash.new { |hash, key| hash[key] = [] } - slides_by_author = Hash.new { |hash, key| hash[key] = [] } - news_by_author = Hash.new { |hash, key| hash[key] = [] } - events_by_author = Hash.new { |hash, key| hash[key] = [] } - videos_by_author = Hash.new { |hash, key| hash[key] = [] } - faqs_by_author = Hash.new { |hash, key| hash[key] = [] } - has_philosophy = Hash.new { false } - - prs_by_author = Hash.new { |hash, key| hash[key] = [] } - reviews_by_author = Hash.new { |hash, key| hash[key] = [] } - - site.data['github'].each do |num, pr| - prs_by_author[pr['author']['login']] << [num, pr['mergedAt']] - - pr['reviews'].each do |review| - reviews_by_author[review['author']['login']] << [num, review['submittedAt'], review['state']] - end + datastructure end - site.pages.each do |t| - # Skip Symlinks - if t.data['symlink'] - next + ## + # This generates the author pages + # Params + # +site+:: The site object + def generate(site) + return unless site.layouts.key? 'contributor_index' + + dir = 'hall-of-fame' + + # pre-calculating this hash saves about 4.9 seconds off the previous + # build time of 5 seconds. + tutorials_by_author = Hash.new { |hash, key| hash[key] = [] } + learning_pathways_by_author = Hash.new { |hash, key| hash[key] = [] } + slides_by_author = Hash.new { |hash, key| hash[key] = [] } + news_by_author = Hash.new { |hash, key| hash[key] = [] } + events_by_author = Hash.new { |hash, key| hash[key] = [] } + videos_by_author = Hash.new { |hash, key| hash[key] = [] } + faqs_by_author = Hash.new { |hash, key| hash[key] = [] } + has_philosophy = Hash.new { false } + + prs_by_author = Hash.new { |hash, key| hash[key] = [] } + reviews_by_author = Hash.new { |hash, key| hash[key] = [] } + + site.data['github'].each do |num, pr| + prs_by_author[pr['author']['login']] << [num, pr['mergedAt']] + + pr['reviews'].each do |review| + reviews_by_author[review['author']['login']] << [num, review['submittedAt'], review['state']] + end end - # Tutorials - pusher(t, tutorials_by_author, false) if t['layout'] == 'tutorial_hands_on' - - # Slides - if !%w[base_slides introduction_slides tutorial_slides].index(t['layout']).nil? - pusher(t, slides_by_author, false) - end + site.pages.each do |t| + # Skip Symlinks + if t.data['symlink'] + next + end - pusher(t, events_by_author, false) if t['layout'] == 'event' + # Tutorials + pusher(t, tutorials_by_author, false) if t['layout'] == 'tutorial_hands_on' - pusher(t, faqs_by_author, false) if t['layout'] == 'faq' + # Slides + if !%w[base_slides introduction_slides tutorial_slides].index(t['layout']).nil? + pusher(t, slides_by_author, false) + end - t.data.fetch('recordings', []).each do |r| - r.fetch('captioners', []).each { |ent| videos_by_author[ent].push([t, 'captioner', r]) } - r.fetch('speakers', []).each { |ent| videos_by_author[ent].push([t, 'speaker', r]) } - end + pusher(t, events_by_author, false) if t['layout'] == 'event' - pusher(t, learning_pathways_by_author, false) if t['layout'] == 'learning-pathway' + pusher(t, faqs_by_author, false) if t['layout'] == 'faq' - # Philosophies - has_philosophy[t.data['username']] = true if t['layout'] == 'training_philosophy' && !t.data['username'].nil? - end + t.data.fetch('recordings', []).each do |r| + r.fetch('captioners', []).each { |ent| videos_by_author[ent].push([t, 'captioner', r]) } + r.fetch('speakers', []).each { |ent| videos_by_author[ent].push([t, 'speaker', r]) } + end - site.posts.docs.each do |t| - # News - pusher(t, news_by_author, true) if t['layout'] == 'news' - end + pusher(t, learning_pathways_by_author, false) if t['layout'] == 'learning-pathway' - Gtn::Contributors.list(site).each_key do |contributor| - # Using PageWithoutAFile instead of a custom class which reads files - # from disk each time, saves some time, but it is unclear how much - # due to how the previous was accounted. But assuming 0.040s per page * 193 should be about 8 seconds. - page2 = PageWithoutAFile.new(site, '', File.join(dir, contributor), 'index.html') - page2.content = nil - name = Gtn::Contributors.fetch_contributor(site, contributor).fetch('name', contributor) - - # Their tutorials - page2.data['contributor'] = contributor - page2.data['personname'] = name - page2.data['title'] = "GTN Contributor: #{name}" - page2.data['layout'] = 'contributor_index' - - page2.data['tutorials'] = tutorials_by_author[contributor].group_by{|x| x[0] }.map{|k, v| [k, v.map{|vv| vv[1]}.compact]} - page2.data['slides'] = slides_by_author[contributor].group_by{|x| x[0] }.map{|k, v| [k, v.map{|vv| vv[1]}.compact]} - page2.data['news'] = news_by_author[contributor] - page2.data['learning_pathways'] = learning_pathways_by_author[contributor] - page2.data['events'] = events_by_author[contributor].group_by{|x| x[0] }.map{|k, v| [k, v.map{|vv| vv[1]}.compact]} - page2.data['videos'] = videos_by_author[contributor].group_by{|x| x[0] }.map{|k, v| [k, v.map{|vv| vv[1]}.uniq.compact]} - page2.data['faqs'] = faqs_by_author[contributor].group_by{|x| x[0] }.map{|k, v| [k, v.map{|vv| vv[1]}.uniq.compact]} - - page2.data['tutorials_count'] = tutorials_by_author[contributor].length - page2.data['slides_count'] = slides_by_author[contributor].length - page2.data['news_count'] = news_by_author[contributor].length - page2.data['learning_pathways_count'] = learning_pathways_by_author[contributor].length - page2.data['events_count'] = events_by_author[contributor].length - page2.data['videos_count'] = videos_by_author[contributor].length - page2.data['faqs_count'] = faqs_by_author[contributor].length - - page2.data['editors'] = TopicFilter.enumerate_topics(site).select do |t| - t.fetch('editorial_board', []).include?(contributor) + # Philosophies + has_philosophy[t.data['username']] = true if t['layout'] == 'training_philosophy' && !t.data['username'].nil? end - # Also their learning pathways - page2.data['editors'] += site.pages.select do |t| - t['layout'] == 'learning-pathway' && t.data.fetch('editorial_board', []).include?(contributor) + + site.posts.docs.each do |t| + # News + pusher(t, news_by_author, true) if t['layout'] == 'news' end - page2.data['editor_count'] = page2.data['editors'].length - page2.data['has_philosophy'] = has_philosophy[contributor] + Gtn::Contributors.list(site).each_key do |contributor| + # Using PageWithoutAFile instead of a custom class which reads files + # from disk each time, saves some time, but it is unclear how much + # due to how the previous was accounted. But assuming 0.040s per page * 193 should be about 8 seconds. + page2 = PageWithoutAFile.new(site, '', File.join(dir, contributor), 'index.html') + page2.content = nil + name = Gtn::Contributors.fetch_contributor(site, contributor).fetch('name', contributor) + + # Their tutorials + page2.data['contributor'] = contributor + page2.data['personname'] = name + page2.data['title'] = "GTN Contributor: #{name}" + page2.data['layout'] = 'contributor_index' + + page2.data['tutorials'] = tutorials_by_author[contributor].group_by{|x| x[0] }.map{|k, v| [k, v.map{|vv| vv[1]}.compact]} + page2.data['slides'] = slides_by_author[contributor].group_by{|x| x[0] }.map{|k, v| [k, v.map{|vv| vv[1]}.compact]} + page2.data['news'] = news_by_author[contributor] + page2.data['learning_pathways'] = learning_pathways_by_author[contributor] + page2.data['events'] = events_by_author[contributor].group_by{|x| x[0] }.map{|k, v| [k, v.map{|vv| vv[1]}.compact]} + page2.data['videos'] = videos_by_author[contributor].group_by{|x| x[0] }.map{|k, v| [k, v.map{|vv| vv[1]}.uniq.compact]} + page2.data['faqs'] = faqs_by_author[contributor].group_by{|x| x[0] }.map{|k, v| [k, v.map{|vv| vv[1]}.uniq.compact]} + + page2.data['tutorials_count'] = tutorials_by_author[contributor].length + page2.data['slides_count'] = slides_by_author[contributor].length + page2.data['news_count'] = news_by_author[contributor].length + page2.data['learning_pathways_count'] = learning_pathways_by_author[contributor].length + page2.data['events_count'] = events_by_author[contributor].length + page2.data['videos_count'] = videos_by_author[contributor].length + page2.data['faqs_count'] = faqs_by_author[contributor].length + + page2.data['editors'] = Gtn::TopicFilter.enumerate_topics(site).select do |t| + t.fetch('editorial_board', []).include?(contributor) + end + # Also their learning pathways + page2.data['editors'] += site.pages.select do |t| + t['layout'] == 'learning-pathway' && t.data.fetch('editorial_board', []).include?(contributor) + end + page2.data['editor_count'] = page2.data['editors'].length + + page2.data['has_philosophy'] = has_philosophy[contributor] - countable_reviews = reviews_by_author[contributor] - .reject{|x| x[1].nil?} # Group by PRs. - .group_by{|x| x[0]}.map{|x, r| r.sort_by{|r1| r1[1]}.max}.sort_by{|w| w[1]}.reverse + countable_reviews = reviews_by_author[contributor] + .reject{|x| x[1].nil?} # Group by PRs. + .group_by{|x| x[0]}.map{|x, r| r.sort_by{|r1| r1[1]}.max}.sort_by{|w| w[1]}.reverse - page2.data['gh_prs_count'] = prs_by_author[contributor].count - page2.data['gh_reviews_count'] = countable_reviews.count + page2.data['gh_prs_count'] = prs_by_author[contributor].count + page2.data['gh_reviews_count'] = countable_reviews.count - page2.data['gh_prs_recent'] = prs_by_author[contributor] - .reject{|x| x[1].nil?}.sort_by { |x| x[1] }.reverse.take(5) - .map{|x| x[0]} - page2.data['gh_reviews_recent'] = countable_reviews.take(5) - .map{|x| x[0]} + page2.data['gh_prs_recent'] = prs_by_author[contributor] + .reject{|x| x[1].nil?}.sort_by { |x| x[1] }.reverse.take(5) + .map{|x| x[0]} + page2.data['gh_reviews_recent'] = countable_reviews.take(5) + .map{|x| x[0]} - site.pages << page2 + site.pages << page2 + end end end end diff --git a/_plugins/by-tag-pages.rb b/_plugins/by-tag-pages.rb index 339594a860a7e8..af315b785d6c34 100644 --- a/_plugins/by-tag-pages.rb +++ b/_plugins/by-tag-pages.rb @@ -1,45 +1,53 @@ require './_plugins/jekyll-topic-filter' module Jekyll - ## - # This class generates the GTN's author pags - class TagPageGenerator < Generator - safe true - + module Generators ## - # This generates the author pages - # Params - # +site+:: The site object - def generate(site) - Jekyll.logger.info '[GTN/SyntheticTopics] Generating By-Tag Indexes' - TopicFilter.list_all_tags(site).map do |tag| - site.data["by_tag_#{tag}"] = { - 'name' => "by_tag_#{tag}", - 'type' => 'use', - 'title' => tag, - 'summary' => "Tutorials covering #{tag}", - 'tag_based' => true, - 'hidden' => true, - } - - topic_index = PageWithoutAFile.new(site, '', "tags/#{Jekyll::Utils.slugify(tag)}", 'index.md') - topic_index.content = '' - topic_index.data['layout'] = 'topic' - topic_index.data['topic_name'] = "by_tag_#{tag}" - topic_index.data['topic'] = site.data["by_tag_#{tag}"] + # This class generates the GTN's Tag Pages + class TagPageGenerator < Generator + safe true - site.pages << topic_index + ## + # This generates the author pages + # Params + # +site+:: The site object + def generate(site) + if Jekyll.env == 'production' + _generate(site) + end end - Jekyll.logger.info '[GTN/SyntheticTopics] Generating By-Tag Embeds' - TopicFilter.list_all_tags(site).map do |tag| - topic_index = PageWithoutAFile.new(site, '', "tags/#{Jekyll::Utils.slugify(tag)}", 'embed.html') - topic_index.content = '' - topic_index.data['layout'] = 'topic-embed' - topic_index.data['topic_name'] = "by_tag_#{tag}" - topic_index.data['topic'] = site.data["by_tag_#{tag}"] + def _generate(site) + Jekyll.logger.info '[GTN/SyntheticTopics] Generating By-Tag Indexes' + Gtn::TopicFilter.list_all_tags(site).map do |tag| + site.data["by_tag_#{tag}"] = { + 'name' => "by_tag_#{tag}", + 'type' => 'use', + 'title' => tag, + 'summary' => "Tutorials covering #{tag}", + 'tag_based' => true, + 'hidden' => true, + } + + topic_index = PageWithoutAFile.new(site, '', "tags/#{Jekyll::Utils.slugify(tag)}", 'index.md') + topic_index.content = '' + topic_index.data['layout'] = 'topic' + topic_index.data['topic_name'] = "by_tag_#{tag}" + topic_index.data['topic'] = site.data["by_tag_#{tag}"] + + site.pages << topic_index + end + + Jekyll.logger.info '[GTN/SyntheticTopics] Generating By-Tag Embeds' + Gtn::TopicFilter.list_all_tags(site).map do |tag| + topic_index = PageWithoutAFile.new(site, '', "tags/#{Jekyll::Utils.slugify(tag)}", 'embed.html') + topic_index.content = '' + topic_index.data['layout'] = 'topic-embed' + topic_index.data['topic_name'] = "by_tag_#{tag}" + topic_index.data['topic'] = site.data["by_tag_#{tag}"] - site.pages << topic_index + site.pages << topic_index + end end end end diff --git a/_plugins/colour-tags.rb b/_plugins/colour-tags.rb index f8a8106d0e2704..a7e0703acf714a 100644 --- a/_plugins/colour-tags.rb +++ b/_plugins/colour-tags.rb @@ -5,48 +5,58 @@ # Our automatic colour tag generator # It makes tags colourful in a reproducible way -module ColourTag - ## - # This function generates the CSS for a colour tag - # Params - # +contents+:: The contents of the tag - # - # Returns - # +String+:: The CSS for the tag - # - # Example - # ColourTag.colour_tag("test") => "--color-primary: #f799ff; --color-darker: #f571ff; --color-dimmed: #f686ff;" - def self.colour_tag(contents) - d = (Digest::SHA256.hexdigest contents).to_i(16) - - hue = ((d >> 4) % 360).abs - lightnessOffset = 75 - lightness = lightnessOffset + (hash & 0xf) - - # randomly make yellow tags bright - lightness += (100 - lightness) * 0.75 if (hue > 70) && (hue < 96) && ((d & 0x100) == 0x100) - - primary = Hsluv.hsluv_to_hex(hue, 100, lightness) - darker = Hsluv.hsluv_to_hex(hue, 100, lightness * 0.9) - dimmed = Hsluv.hsluv_to_hex(hue, 100, lightness * 0.95) - - "--color-primary: #{primary}; --color-darker: #{darker}; --color-dimmed: #{dimmed};" +module Gtn + module HashedColours + ## + # This function generates the CSS for a colour tag + # Params + # +contents+:: The contents of the tag + # + # Returns + # +String+:: The CSS for the tag + # + # Example + # HashedColours.colour_tag("test") => "--color-primary: #f799ff; --color-darker: #f571ff; --color-dimmed: #f686ff;" + def self.colour_tag(contents) + d = (Digest::SHA256.hexdigest contents).to_i(16) + + hue = ((d >> 4) % 360).abs + lightnessOffset = 75 + lightness = lightnessOffset + (hash & 0xf) + + # randomly make yellow tags bright + lightness += (100 - lightness) * 0.75 if (hue > 70) && (hue < 96) && ((d & 0x100) == 0x100) + + primary = Hsluv.hsluv_to_hex(hue, 100, lightness) + darker = Hsluv.hsluv_to_hex(hue, 100, lightness * 0.9) + dimmed = Hsluv.hsluv_to_hex(hue, 100, lightness * 0.95) + + "--color-primary: #{primary}; --color-darker: #{darker}; --color-dimmed: #{dimmed};" + end end end module Jekyll # :nodoc: - # The jekyll implementation of the colour tag - module ImplColourTag - def cache - @@cache ||= Jekyll::Cache.new('ColorTags') - end + module Filters + # The jekyll implementation of the colour tag + module HashedColours + def cache + @@cache ||= Jekyll::Cache.new('ColorTags') + end - def colour_tag(contents) - cache.getset(contents) do - ColourTag.colour_tag(contents) + ## + # colour_tag will turn some contents into a set of CSS variables for use in CSS. + # + # Examples: + # + # {{ tag }} + def colour_tag(contents) + cache.getset(contents) do + Gtn::HashedColours.colour_tag(contents) + end end end end end -Liquid::Template.register_filter(Jekyll::ImplColourTag) +Liquid::Template.register_filter(Jekyll::Filters::HashedColours) diff --git a/_plugins/feeds.rb b/_plugins/feeds.rb index 1c960301a89b99..d934634795cbc9 100644 --- a/_plugins/feeds.rb +++ b/_plugins/feeds.rb @@ -6,6 +6,12 @@ require 'json' class DateTime + ## + # Convert a given DateTime stamp roughly to an African/European lunch time + # + # Why that time? It is when the majority of our users are online and wanting to read the news. + # + # This is really only available in the feeds plugin, should not be assumed to be available elsewhere. def to_euro_lunch self.to_date.to_datetime + 0.6 end @@ -37,30 +43,6 @@ def track(url) end end -def objectify(attrs, url, path) - obj = attrs.clone - obj['__path'] = path - obj['__url'] = url - - def obj.data - self - end - - def obj.path - self['__path'] - end - - def obj.url - self['__url'] - end - - def obj.content - self.fetch('content', 'NO CONTENT AVAILABLE') - end - - obj -end - FEED_WIDGET_XSLT = Nokogiri::XSLT(File.read('feed-widget.xslt.xml')) def serialise(site, feed_path, builder) @@ -215,135 +197,6 @@ def generate_tag_topic_feeds(_site) '' end -def all_date_sorted_materials(site) - events = site.pages.select { |x| x['layout'] == 'event' || x['layout'] == 'event-external' } - materials = TopicFilter.list_all_materials(site).reject { |k, _v| k['draft'] } - news = site.posts.select { |x| x['layout'] == 'news' } - faqs = site.pages.select { |x| x['layout'] == 'faq' } - pathways = site.pages.select { |x| x['layout'] == 'learning-pathway' } - workflows = Dir.glob('topics/**/*.ga') - - bucket = events.map do |e| - [Gtn::PublicationTimes.obtain_time(e.path).to_datetime, 'events', e, ['event'] + e.data.fetch('tags', [])] - end - - materials.each do |m| - tags = [m['topic_name']] + (m['tags'] || []) - m.fetch('ref_tutorials', []).map do |t| - bucket << [Gtn::PublicationTimes.obtain_time(t.path).to_datetime, 'tutorials', t, tags] - - (t['recordings'] || []).map do |r| - url = '/' + t.path.gsub(/tutorial(_[A_Z_]*)?.(html|md)$/, 'recordings/') - url += "#tutorial-recording-#{Date.parse(r['date']).strftime('%-d-%B-%Y').downcase}" - attr = {'title' => "Recording of " + t['title'], - 'contributors' => r['speakers'] + (r['captions'] || []), - 'content' => "A #{r['length']} long recording is now available."} - - obj = objectify(attr, url, t.path) - bucket << [DateTime.parse(r['date'].to_s), 'recordings', obj, tags] - end - end - - - - m.fetch('ref_slides', []).reject { |s| s.url =~ /-plain.html/ }.map do |s| - bucket << [Gtn::PublicationTimes.obtain_time(s.path).to_datetime, 'slides', s, tags] - - (s['recordings'] || []).map do |r| - url = '/' + s.path.gsub(/slides(_[A_Z_]*)?.(html|md)$/, 'recordings/') - url += "#tutorial-recording-#{Date.parse(r['date']).strftime('%-d-%B-%Y').downcase}" - attr = {'title' => "Recording of " + s['title'], - 'contributors' => r['speakers'] + (r['captions'] || []), - 'content' => "A #{r['length']} long recording is now available."} - obj = objectify(attr, url, s.path) - bucket << [DateTime.parse(r['date'].to_s), 'recordings', obj, tags] - end - end - end - - bucket += news.map do |n| - [n.date.to_datetime, 'news', n, ['news'] + n.data.fetch('tags', [])] - end - - bucket += faqs.map do |n| - tag = Gtn::PublicationTimes.clean_path(n.path).split('/')[1] - [Gtn::PublicationTimes.obtain_time(n.path).to_datetime, 'faqs', n, ['faqs', tag]] - end - - bucket += pathways.map do |n| - tags = ['learning-pathway'] + (n['tags'] || []) - [Gtn::PublicationTimes.obtain_time(n.path).to_datetime, 'learning-pathways', n, tags] - end - - bucket += workflows.map do |n| - tag = Gtn::PublicationTimes.clean_path(n).split('/')[1] - wf_data = JSON.parse(File.read(n)) - - attrs = { - 'title' => wf_data['name'], - 'description' => wf_data['annotation'], - 'tags' => wf_data['tags'], - 'contributors' => wf_data.fetch('creator', []).map do |c| - matched = site.data['contributors'].select{|k, v| - v.fetch('orcid', "does-not-exist") == c.fetch('identifier', "").gsub('https://orcid.org/', '') - }.first - if matched - matched[0] - else - c['name'] - end - end - } - - # These aren't truly stable. I'm not sure what to do about that. - obj = objectify(attrs, '/' + n.gsub(/\.ga$/, '.html'), n) - # obj = objectify(attrs, '/' + n.path[0..n.path.rindex('/')], n) - - - [Gtn::PublicationTimes.obtain_time(n).to_datetime, 'workflows', obj, ['workflows', tag] + obj['tags']] - end - - # Remove symlinks from bucket. - bucket = bucket.reject { |date, type, page, tags| - File.symlink?(page.path) || File.symlink?(File.dirname(page.path)) || File.symlink?(File.dirname(File.dirname(page.path))) - } - - bucket += site.data['contributors'].map do |k, v| - a = {'title' => "@#{k}", - 'content' => "GTN Contributions from #{k}"} - obj = objectify(a, "/hall-of-fame/#{k}/", k) - - [DateTime.parse("#{v['joined']}-01T12:00:00", 'content' => "GTN Contributions from #{k}"), 'contributors', obj, ['contributor']] - end - - bucket += site.data['grants'].map do |k, v| - a = {'title' => "@#{k}", - 'content' => "GTN Contributions from #{k}"} - obj = objectify(a, "/hall-of-fame/#{k}/", k) - - # TODO: backdate grants, organisations - if v['joined'] - [DateTime.parse("#{v['joined']}-01T12:00:00"), 'grants', obj, ['grant']] - end - end.compact - - bucket += site.data['organisations'].map do |k, v| - a = {'title' => "@#{k}", - 'content' => "GTN Contributions from #{k}"} - obj = objectify(a, "/hall-of-fame/#{k}/", k) - - if v['joined'] - [DateTime.parse("#{v['joined']}-01T12:00:00"), 'organisations', obj, ['organisation']] - end - end.compact - - bucket - .reject{|x| x[0] > DateTime.now } # Remove future-dated materials - .reject{|x| x[2]['draft'] == true } # Remove drafts - .sort_by {|x| x[0] } # Date-sorted, not strictly necessary since will be grouped. - .reverse -end - def group_bucket_by(bucket, group_by: 'day') case group_by when 'day' @@ -736,12 +589,12 @@ def generate_event_feeds(site) {title: 'Events', url: "#{site.config['url']}#{site.baseurl}/events/feed.xml"} ] - bucket = all_date_sorted_materials(site) + bucket = Gtn::TopicFilter.all_date_sorted_resources(site) bucket.freeze opml['GTN Topics'] = [] opml['GTN Topics - Digests'] = [] - TopicFilter.list_topics(site).each do |topic| + Gtn::TopicFilter.list_topics(site).each do |topic| generate_topic_feeds(site, topic, bucket) opml['GTN Topics'] << {title: "#{topic} all changes", url: "#{site.config['url']}#{site.baseurl}/topic/feed.xml"} diff --git a/_plugins/file_exists.rb b/_plugins/file_exists.rb index 10b6b8cc2b3eda..4f27b6da096326 100644 --- a/_plugins/file_exists.rb +++ b/_plugins/file_exists.rb @@ -1,28 +1,32 @@ module Jekyll - ## - # This class adds a tag that checks if a file exists. - class FileExistsTag < Liquid::Tag - def initialize(tag_name, path, tokens) # :nodoc: - super - @path = path - end - + module Tags ## - # Render the tag - # Params: - # +context+:: The context of the page - def render(context) - # Pipe parameter through Liquid to make additional replacements possible - url = Liquid::Template.parse(@path).render context + # This class adds a tag that checks if a file exists. + class FileExistsTag < Liquid::Tag + def initialize(tag_name, path, tokens) # :nodoc: + super + @path = path + end + + ## + # file_exists - Check if a file exists and return an appropriate boolean + # + # Examples: + # + # {% capture hasfaq %}{% file_exists {{faqpage}} %}{% endcapture %} + def render(context) + # Pipe parameter through Liquid to make additional replacements possible + url = Liquid::Template.parse(@path).render context - # Adds the site source, so that it also works with a custom one - site_source = context.registers[:site].config['source'] - file_path = "#{site_source}/#{url}" + # Adds the site source, so that it also works with a custom one + site_source = context.registers[:site].config['source'] + file_path = "#{site_source}/#{url}" - # Check if file exists (returns true or false) - File.exist?(file_path.strip!).to_s + # Check if file exists (returns true or false) + File.exist?(file_path.strip!).to_s + end end end end -Liquid::Template.register_tag('file_exists', Jekyll::FileExistsTag) +Liquid::Template.register_tag('file_exists', Jekyll::Tags::FileExistsTag) diff --git a/_plugins/generator-recordings.rb b/_plugins/generator-recordings.rb index 1d592c760c27e6..8710416a4c1448 100644 --- a/_plugins/generator-recordings.rb +++ b/_plugins/generator-recordings.rb @@ -3,33 +3,35 @@ require './_plugins/gtn' module Jekyll - ## - # This class generates the GTN's author pags - class RecordingPageGenerator < Generator - safe true - + module Generators ## - # This generates the recording pages, where needed. - # Params - # +site+:: The site object - def generate(site) - Jekyll.logger.info "[GTN/Videos] Generating recording pages" - materials = TopicFilter - .list_all_materials(site) + # This class generates the GTN's recording pages + class RecordingPageGenerator < Generator + safe true + + ## + # This generates the recording pages, where needed. + # Params + # +site+:: The site object + def generate(site) + Jekyll.logger.info "[GTN/Videos] Generating recording pages" + materials = Gtn::TopicFilter + .list_all_materials(site) - with_video = materials - .select{|m| m.has_key? 'recordings' or m.has_key? 'slide_recordings'} + with_video = materials + .select{|m| m.has_key? 'recordings' or m.has_key? 'slide_recordings'} - Jekyll.logger.info "[GTN/Videos] #{with_video.length} materials with recordings found." - materials.each do |material| - page2 = PageWithoutAFile.new(site, '', material['dir'], 'recordings/index.html') - page2.content = nil - page2.data['layout'] = 'recordings' - page2.data['topic_name'] = material['topic_name'] - page2.data['tutorial_name'] = material['tutorial_name'] - page2.data['material'] = material - page2.data['title'] = 'Recordings for ' + material['title'] - site.pages << page2 + Jekyll.logger.info "[GTN/Videos] #{with_video.length} materials with recordings found." + materials.each do |material| + page2 = PageWithoutAFile.new(site, '', material['dir'], 'recordings/index.html') + page2.content = nil + page2.data['layout'] = 'recordings' + page2.data['topic_name'] = material['topic_name'] + page2.data['tutorial_name'] = material['tutorial_name'] + page2.data['material'] = material + page2.data['title'] = 'Recordings for ' + material['title'] + site.pages << page2 + end end end end diff --git a/_plugins/generator-workflows.rb b/_plugins/generator-workflows.rb index 3470d03b296b73..02f12298c57093 100644 --- a/_plugins/generator-workflows.rb +++ b/_plugins/generator-workflows.rb @@ -4,54 +4,82 @@ module Jekyll ## - # This class generates the GTN's author pags - class WorkflowPageGenerator < Generator - safe true + # {Jekyll Generators}[https://jekyllrb.com/docs/plugins/generators/] are a way to let you generate files at runtime, without needing them to exist on disk. + # + # We use generators for lots of purposes, e.g. + # + # Real Generators, the way Jekyll intended: + # + # - Jekyll::Generators::PlaintextSlidesGenerator - turns slides.html into plain text non-JS versions. + # - Jekyll::Generators::RecordingPageGenerator - emits a webpage for every tutorial that has recordings, in the GTN + # - Jekyll::Generators::WorkflowPageGenerator - emits a webpage for every workflow in the GTN + # - Jekyll::Generators::AuthorPageGenerator - emits a hall-of-fame entry for every contributor, organisation, and grant listed in our site metadata. + # - Jekyll::Generators::RmarkdownGenerator - outputs the RMarkdown notebooks for tutorials that want them. + # - Jekyll::Generators::SitemapGenerator2 - alternative for the jekyll-sitemap plugin that's a bit faster. + # - Jekyll::Generators::SyntheticTopicGenerator - our synthetic tag based topics + # - Jekyll::Generators::TagPageGenerator - topic page for every tag + # - Jekyll::Generators::WorkflowPageGenerator + # + # Muck with page contents generators (probably should be hooks): + # + # - Jekyll::Generators::Abbreviate - turns +{something}+ into an abbreviation tag + # - Jekyll::Generators::Figurify - handles our modified markdown for images + # - Jekyll::Generators::Boxify - turns +Redirecting…
+ Click here if you are not redirected. + + REDIR + def self.mapped?(tutorial, current_mapping) current_mapping['id'].values.include? tutorial end + ## + # Duplicate of the jekyll-redirect-from plugin template. + # We can't use that for, reasons. + def self.html_redirect(target) + REDIRECT_TEMPLATE.gsub('REDIRECT_URL', target) + end + + ## + # Fix missing symlinks (usually exist because the target file has been + # renamed and doesn't exist anymore.) However, a redirect *will* be present + # for the original filename so we just fix the missing symlink. + # + # Params: + # +site+:: The Jekyll site object + def self.fix_missing_redirs(site) + missing_redirs = site.data['shortlinks']['id'].select do |id, target| + short_link = "short/#{id}.html" + ! File.exist?(site.in_dest_dir(short_link)) + end + + missing_redirs.each do |id, target| + short_link = "short/#{id}.html" + Jekyll.logger.warn "[GTN/Shortlink]" "Shortlink target #{target} does not exist for shortlink #{short_link}, fixing." + File.write(site.in_dest_dir(short_link), Gtn::Shortlinks.html_redirect(target)) + end + end + def self.update(current_mapping) current_mapping['id'] = {} if !current_mapping.key? 'id' diff --git a/_plugins/gtn/supported.rb b/_plugins/gtn/supported.rb index 0617c7c189fba6..e898ad5e50c60f 100644 --- a/_plugins/gtn/supported.rb +++ b/_plugins/gtn/supported.rb @@ -72,17 +72,17 @@ def self.calculate(data, tool_list) # generate a 'false' value when merging sets. inexact_support -= exact_support - usegalaxy_server_urls = Gtn::Usegalaxy.servers.map { |x| x[:url] } + usegalaxy_server_urls = Gtn::Usegalaxy.servers.map { |x| x[:url].downcase.gsub(/\/$/, '')} { 'exact' => (exact_support || []).map do |id| data['servers'][id].update( - { 'usegalaxy' => usegalaxy_server_urls.include?(data['servers'][id]['url']) } + { 'usegalaxy' => usegalaxy_server_urls.include?(data['servers'][id]['url'].downcase.gsub(/\/$/, '')) } ) end, 'inexact' => (inexact_support || []).map do |id| data['servers'][id].update( - { 'usegalaxy' => usegalaxy_server_urls.include?(data['servers'][id]['url']) } + { 'usegalaxy' => usegalaxy_server_urls.include?(data['servers'][id]['url'].downcase.gsub(/\/$/, '')) } ) end } @@ -183,7 +183,7 @@ def self.calculate_matrix(data, tool_list) if ARGV.length.positive? && (ARGV[0] == 'test') require 'test/unit' # Testing for the class - class IntersectionTest < Test::Unit::TestCase + class Gtn::Test::IntersectionTest < Test::Unit::TestCase def test_exact data = { 'servers' => { 0 => 's0', 1 => 's1', 2 => 's2' }, diff --git a/_plugins/gtn/synthetic.rb b/_plugins/gtn/synthetic.rb index 29f748da8b360d..38efd8fd9b3b02 100644 --- a/_plugins/gtn/synthetic.rb +++ b/_plugins/gtn/synthetic.rb @@ -3,26 +3,28 @@ require './_plugins/jekyll-topic-filter' module Jekyll - # Generates synthetic topics from tutorials with specific tags - class SyntheticTopicGenerator < Generator - def generate(site) - # Full Bibliography - Jekyll.logger.info '[GTN/SyntheticTopics] Generating Indexes' + module Generators + # Generates synthetic topics from tutorials with specific tags + class SyntheticTopicGenerator < Generator + def generate(site) + # Full Bibliography + Jekyll.logger.info '[GTN/SyntheticTopics] Generating Indexes' - TopicFilter.list_topics(site).select { |t| site.data[t]['tag_based'] }.each do |topic| - Jekyll.logger.debug "[GTN/SyntheticTopics] Creating #{topic} topic" + Gtn::TopicFilter.list_topics(site).select { |t| site.data[t]['tag_based'] }.each do |topic| + Jekyll.logger.debug "[GTN/SyntheticTopics] Creating #{topic} topic" - topic_index = PageWithoutAFile.new(site, '', "topics/#{topic}", 'index.md') - topic_index.content = '' - topic_index.data['layout'] = 'topic' - topic_index.data['topic_name'] = topic - site.pages << topic_index + topic_index = PageWithoutAFile.new(site, '', "topics/#{topic}", 'index.md') + topic_index.content = '' + topic_index.data['layout'] = 'topic' + topic_index.data['topic_name'] = topic + site.pages << topic_index - # For now, intentionally no FAQ - # faq_index = PageWithoutAFile.new(site, "", "topics/#{topic}/faqs", "index.md") - # faq_index.content = "" - # faq_index.data["layout"] = "faq-page" - # site.pages << faq_index + # For now, intentionally no FAQ + # faq_index = PageWithoutAFile.new(site, "", "topics/#{topic}/faqs", "index.md") + # faq_index.content = "" + # faq_index.data["layout"] = "faq-page" + # site.pages << faq_index + end end end end diff --git a/_plugins/jekyll-boxify.rb b/_plugins/jekyll-boxify.rb index 192bac83973cea..ed712064942a49 100644 --- a/_plugins/jekyll-boxify.rb +++ b/_plugins/jekyll-boxify.rb @@ -4,71 +4,73 @@ require './_plugins/gtn' module Jekyll - # The GTN Box generation process - class Boxify < Jekyll::Generator - def initialize(config) # :nodoc: - super - @config = config['boxify'] ||= {} - end + module Generators + # The GTN Box generation process + class Boxify < Jekyll::Generator + def initialize(config) # :nodoc: + super + @config = config['boxify'] ||= {} + end - def generate(site) # :nodoc: - Jekyll.logger.info '[GTN/Boxify]' - site.pages.each { |page| boxify page, site } - site.posts.docs.each { |post| boxify post, site } - end + def generate(site) # :nodoc: + Jekyll.logger.info '[GTN/Boxify]' + site.pages.each { |page| boxify page, site } + site.posts.docs.each { |post| boxify post, site } + end - ## - # This function adds boxes to the page content. - # Params: - # +page+:: The page to add boxes to - # +site+:: The +Jekyll::Site+ object - def boxify(page, _site) - return if page.content.nil? + ## + # This function adds boxes to the page content. + # Params: + # +page+:: The page to add boxes to + # +site+:: The +Jekyll::Site+ object + def boxify(page, _site) + return if page.content.nil? - lang = page['lang'] || 'en' + lang = page['lang'] || 'en' - # Interim solution, fancier box titles - # rubocop:disable Layout/LineLength - page.content = page.content.gsub(%r{<(?- '
- out += sorted_citations.map do |c|
- r = Gtn::Scholar.render_citation(c)
- %(
- '
+ out += sorted_citations.map do |c|
+ r = Gtn::Scholar.render_citation(c)
+ %(
\1
\2')
- .gsub(/([^`])`([^`]*)` /, '\1\2
')
+ # Here we replace individual `s with codeblocks, they screw up
+ # rendering otherwise by going through rouge
+ source = source.gsub(/ `([^`]*)`([^`])/, ' \1
\2')
+ .gsub(/([^`])`([^`]*)` /, '\1\2
')
- # Strip out includes, snippets
- source.gsub!(/{% include .* %}/, '')
- source.gsub!(/{% snippet .* %}/, '')
+ # Strip out includes, snippets
+ source.gsub!(/{% include .* %}/, '')
+ source.gsub!(/{% snippet .* %}/, '')
- # Replace all the broken icons that can't render, because we don't
- # have access to the full render pipeline.
- cell['source'] = markdownify(site, source)
+ # Replace all the broken icons that can't render, because we don't
+ # have access to the full render pipeline.
+ cell['source'] = markdownify(site, source)
- ICONS.each do |key, val|
- # Replace the new box titles with h3s.
- cell['source'].gsub!(%r{