Skip to content

Commit

Permalink
Merge pull request #2787 from galaxyproject/bash
Browse files Browse the repository at this point in the history
Topic: Data Science Survival Kit + GTN notebook Support
  • Loading branch information
shiltemann authored Sep 30, 2021
2 parents 8fe6855 + 6d1b455 commit 727f741
Show file tree
Hide file tree
Showing 47 changed files with 9,141 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- uses: actions/setup-python@v2
with:
python-version: '3.7'
architecture: 'x64'
- uses: actions/setup-ruby@v1
with:
ruby-version: "2.5"
Expand All @@ -39,6 +43,7 @@ jobs:
gem install bundler
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
pip install -r requirements.txt
- run: bundle exec jekyll build --strict_front_matter -d _site/training-material
- run: |
bundle exec htmlproofer \
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jobs:
gem install bundler
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
pip install -r requirements.txt
- name: Build Site
run: |
Expand Down
5 changes: 4 additions & 1 deletion CONTRIBUTORS.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ khanteymoori:
email: khanteymoori@gmail.com
orcid: 0000-0001-6811-9196
joined: 2019-07

KlemensFroehlich:
name: Klemens Fröhlich
joined: 2021-08
Expand Down Expand Up @@ -944,3 +944,6 @@ yvanlebras:
gitter: yvanlebras
orcid: 0000-0002-8504-068X
joined: 2017-09

carpentries:
name: The Carpentries
1 change: 1 addition & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ icon-tag:
license: fas fa-balance-scale
linkedin: fab fa-linkedin
new-history: fas fa-plus
notebook: far fa-file-code
objectives: fas fa-bullseye
orcid: fab fa-orcid
param-check: far fa-check-square
Expand Down
7 changes: 7 additions & 0 deletions _includes/resource-notebooks.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% assign locale = site.data.lang[page.lang] %}

{% if include.material.notebook %}
<a class="topic-icon" href="{{ site.baseurl }}/topics/{{ include.topic }}/tutorials/{{ include.material.tutorial_name }}/tutorial.md.ipynb" title="Notebooks" alt="{{ include.material.title }} workflows">
{% icon notebook aria=false %}{% if include.label %} {{locale['notebooks'] | default: "Jupyter Notebook"}}{% endif %}
</a>
{% endif %}
22 changes: 22 additions & 0 deletions _layouts/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,28 @@ <h2>Galaxy Tips & Tricks</h2>
</tbody>
</table>

<h2>Data Science Survival Kit</h2>
<table class="table table-striped">
<thead>
<tr>
<th>Topic</th>
<th>Tutorials</th>
</tr>
</thead>
<tbody>
{% for topic in sorted_topics %}
{% if topic[1].type == "data-science" and topic[1].enable != false %}
<tr>
{% assign tutorial_number = site.pages | topic_filter:topic[1].name | topic_count %}
<td><a href="{{ site.baseurl }}/topics/{{ topic[1].name }}/">{{ topic[1].title }}</a></td>
<td>{{ tutorial_number }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>


<h2>Galaxy for Developers and Admins</h2>
<table class="table table-striped">
<thead>
Expand Down
27 changes: 26 additions & 1 deletion _layouts/tutorial_hands_on.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ <h3>{{ locale['overview'] | default: "Overview"}}</h3>
{% assign tuto_has_zenodo = true %}
{% endif %}

{% if associated_slides or intro_link or tuto_has_zenodo or own_material.workflows or own_material.tours or tuto_has_docker %}
{% if associated_slides or intro_link or tuto_has_zenodo or own_material.workflows or own_material.tours or tuto_has_docker or material.notebook %}
<div id="supporting-materials"><strong><i class="fa fa-external-link" aria-hidden="true"></i> {{ locale['supporting-materials'] | default: "Supporting Materials" }}:</strong></div>
<ul class="supporting_material">
{% if associated_slides %}
Expand All @@ -131,6 +131,10 @@ <h3>{{ locale['overview'] | default: "Overview"}}</h3>
<li class="btn btn-default supporting_material">{% include _includes/resource-tours.html material=own_material topic=topic.name label=true %}</li>
{% endif %}

{% if material.notebook %}
<li class="btn btn-default supporting_material">{% include _includes/resource-notebooks.html material=material topic=topic.name label=true %}</li>
{% endif %}

{% assign faqpage = page.dir | append: 'faqs/index.md' | remove_first: '/' %}
{% capture hasfaq %}{% file_exists {{faqpage}} %}{% endcapture %}
{% if hasfaq == "true" %}
Expand All @@ -157,6 +161,27 @@ <h3>{{ locale['overview'] | default: "Overview"}}</h3>
<nav id="toc" data-toggle="toc" class="sticky-top" aria-label="Table of Contents"></nav>
</div>
<div class="col-sm-10">
{% if page.notebook %}
<blockquote class="comment">
<h3>{% icon comment aria=false %} {{locale['best-in-jupyter'] | default: "Best viewed in a Jupyter Notebook" }}</h3>
<p>
This tutorial is best viewed in a Jupyter notebook! You can load this notebook in Jupyter on one of the UseGalaxy.* servers, or you can click to run it in MyBinder
</p>
<p><b>Launching on MyBinder</b>
Click here and the notebook will launch on MyBinder.org
<a href="https://mybinder.org/v2/gh/galaxyproject/training-material/main?filepath={{ site.baseurl | replace: '/','' }}%2F{{ page.path | replace: '/','%2F' }}.ipynb" title="Launch notebook on MyBinder"><img src="https://mybinder.org/badge_logo.svg" alt="My Binder Logo"/></a>
</p>
<p><b>Launching the notebook in Jupyter in Galaxy</b>
<ol>
<li><a href="{% link faqs/galaxy/interactive_tools_jupyter_launch.md %}">Instructions to Launch JupyterLab</a></li>
<li>Open a Terminal in JupyterLab with <b>File -> New -> Terminal</b></li>
<li>Run <code>wget {{ site.url }}{{ site.baseurl }}/{{ page.path }}.ipynb</code></li>
<li>Select the notebook that appears in the list of files on the left.</li>
</ol>
</p>
</blockquote>
{% endif %}

{{ content
| replace: '<blockquote class="hands_on">', '<blockquote class="notranslate hands_on">'
| no_translate:site.words_to_not_translate }}
Expand Down
236 changes: 236 additions & 0 deletions _plugins/notebook-page.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
require 'json'
require 'mkmf'
require 'fileutils'
require 'yaml'
require "kramdown"

module MakeMakefile::Logging
@logfile = File::NULL
end

module Jekyll
class JupyterNotebookGenerator < Generator
safe true

def convertToNotebook(tutorial, language)
notebook_json = `notedown --match=#{language} #{tutorial}`
JSON.parse(notebook_json)
end

def extractAndFixMetadataCell(notebook)
# Strip out %%bash since we'll use the bash kernel
metadata = nil
contributors = nil

File.open('CONTRIBUTORS.yaml', 'r') do |f2|
contributors = YAML.load(f2.read)
end

notebook['cells'].map.with_index {|cell, i|
if i == 0
metadata = YAML.load(cell['source'].join(''))
offset = cell['source'].slice(1..-1).index("---\n")

by_line = metadata['contributors'].map{|c|
"[#{contributors.fetch(c, {"name" => c}).fetch('name', c)}](https://training.galaxyproject.org/hall-of-fame/#{c}/)"
}.join(", ")

meta_header = [
"<div style=\"border: 2px solid #8A9AD0; margin: 1em 0.2em; padding: 0.5em;\">\n\n",
"# #{metadata['title']}\n",
"\n",
"by #{by_line}\n",
"\n",
"**Objectives**\n",
"\n",
] + metadata['questions'].map{|q| "- #{q}\n"} + [
"\n",
"**Objectives**\n",
"\n",
] + metadata['objectives'].map{|q| "- #{q}\n"} + [
"\n",
"**Time Estimation: #{metadata['time_estimation']}**\n",
"\n",
"</div>\n",
]
cell['source'] = meta_header + cell['source'].slice(offset + 2..-1)
end
cell
}
return notebook, metadata
end

def fixBashNotebook(notebook)
# Set the bash kernel
notebook['metadata'] = {
"kernelspec" => {
"display_name" => "Bash",
"language" => "bash",
"name" => "bash"
},
"language_info" => {
"codemirror_mode" => "shell",
"file_extension" => ".sh",
"mimetype" => "text/x-sh",
"name" => "bash"
}
}
# Strip out %%bash since we'll use the bash kernel
notebook['cells'].map{|cell|
if cell.fetch('cell_type') == 'code'
if cell['source'][0] == "%%bash\n"
cell['source'] = cell['source'].slice(1..-1)
end
end
cell
}
notebook
end

def markdownify(site, text)
site.find_converter_instance(
Jekyll::Converters::Markdown
).convert(text.to_s)
end

def renderMarkdownCells(site, notebook, page)
# TODO:
# - strip agenda
# - strip yaml metadata header (or render it more nicely.)

colors = {
"overview" => "#8A9AD0",
"agenda" => "#86D486; display: none",
"keypoints" => "#FFA1A1",
"tip" => "#FFE19E",
"warning" => "#de8875",
"comment" => "#ffecc1",
"hands_on" => "#dfe5f9",
"question" => "#8A9AD0",
"solution" => "#B8C3EA; color: white",
"details" => "#ddd",
"feedback" => "#86D486",
"code-in" => "#86D486",
"code-out" => "#fb99d0",
}

notebook['cells'].map{|cell|
if cell.fetch('cell_type') == 'markdown'

# The source is initially a list of strings, we'll merge it together
# to make it easier to work with.
source = cell['source'].join("")

# Here we replace individual `s with codeblocks, they screw up
# rendering otherwise by going through rouge
source = source.gsub(/ `([^`]*)`([^`])/, ' <code>\1</code>\2')
.gsub(/([^`])`([^`]*)` /, '\1<code>\2</code> ')

# 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)
.gsub(/{% icon tip %}/, '💡')
.gsub(/{% icon code-in %}/, '⌨️')
.gsub(/{% icon code-out %}/, '🖥')
.gsub(/{% icon question %}/, '❓')
.gsub(/{% icon solution %}/, '👁')
.gsub(/{% icon warning %}/, '⚠️')
.gsub(/{% icon comment %}/, '💬')
.gsub(/{% icon hands_on %}/, '✏️')

# Here we give a GTN-ish styling that doesn't try to be too faithful,
# so we aren't spending time keeping up with changes to GTN css,
# we're making it 'our own' a bit.

colors.each{ |key, val|
cell['source'].gsub!(/<blockquote class="#{key}">/, "<blockquote class=\"#{key}\" style=\"border: 2px solid #{val}; margin: 1em 0.2em\">")
}

# Images are referenced in the GTN through relative URLs which is
# fab, but in a notebook this doesn't make sense as it will live
# outside of the GTN. We need real URLs.
#
# But we'll only do this if it's in "deploy" mode, in order to have
# the images also work locally.
if site.config['url'] == "https://training.galaxyproject.org"
cell['source'].gsub!(/<img src=\"\.\./, '<img src="' + site.config['url'] + site.config['baseurl'] + page.url.split('/')[0..-2].join('/') + '/..')
end

# Strip out the highlighting as it is bad on some platforms.
cell['source'].gsub!(/<pre class="highlight">/, '<pre>')
cell['source'].gsub!(/<div class="highlight">/, '<div>')

# add a 'hint' to the solution boxes which have blanked out text.
cell['source'].gsub!(/(<h3 id="-icon-solution--solution">)/, '<div style="color: #555; font-size: 95%;">Hint: Select the text with your mouse to see the answer</div>\1')

# There is some weirdness in the processing of $s in Jupyter. After a
# certain number of them, it will give up, and just render everything
# like with a '<pre>'. We remove this to prevent that result.
cell['source'].gsub!(/^\s*</, '<')
# Additionally leading spaces are sometimes interpreted as <pre>s and
# end up causing paragraphs to be rendered as code. So we wipe out
# all leading space.
cell['source'].gsub!(/\$/, '&#36;')
end
cell
}
notebook
end


def generate(site)
if find_executable('notedown').nil?
puts "We could not find the notedown executable, so, notebooks will not be rendered."
return
end

# For every tutorial with the 'notebook' key in the page data
site.pages.select{|page| page.data['layout'] == 'tutorial_hands_on' and page.data.has_key?('notebook')}.each do |page|
# We get the path to the tutorial source
dir = File.dirname(File.join('.', page.url))
fn = File.join('.', page.url).sub(/html$/, 'md')
notebook_language = page.data['notebook'].fetch('language', 'python')

# Here we read use `notedown` to convert the tutorial to a Hash
# representing the notebook
notebook = convertToNotebook(fn, notebook_language)

# This extracts the metadata yaml header and does manual formatting of
# the header data to make for a nicer notebook.
notebook, metadata = extractAndFixMetadataCell(notebook)

# Apply language specific conventions
if notebook_language == 'bash'
notebook = fixBashNotebook(notebook)
end

# Here we loop over the markdown cells and render them to HTML. This
# allows us to get rid of classes like {: .tip} that would be left in
# the output by Jupyter's markdown renderer, and additionally do any
# custom CSS which only seems to work when inline on a cell, i.e. we
# can't setup a style block, so we really need to render the markdown
# to html.
notebook = renderMarkdownCells(site, notebook, page)

# Here we add a close to the notebook
notebook['cells'] = notebook['cells'] + [{
"cell_type" => "markdown",
"id" => "final-ending-cell",
"metadata" => {},
"source" => [
"# Key Points\n\n",
] + metadata['key_points'].map{|k| "- #{k}\n"} + [
"\n# Congratulations on successfully completing this tutorial!\n\n",
"Please [fill out the feedback on the GTN website](https://training.galaxyproject.org/training-material#{page.url}#feedback) and check there for further resources!\n"
]
}]

# Create the JSON file and inject the data
page2 = PageWithoutAFile.new(site, "", dir, "tutorial.md.ipynb")
page2.content = JSON.pretty_generate(notebook)
page2.data["layout"] = nil
site.pages << page2
end
end
end
end
4 changes: 2 additions & 2 deletions bin/ari-make.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ trap cleanup EXIT

# We have an old commit ID, so we need to figure out which slides to build.
if [[ "${PREVIOUS_COMMIT_ID}" != "none" ]]; then
changed_slides="$(join <(find topics -name 'slides.html' -or -name introduction.html -or -name 'slides_ES.html' | xargs ./bin/filter-has-videos | sort) <(git diff ${PREVIOUS_COMMIT_ID} --name-only | sort))"
changed_slides="$(join <(find topics -name 'slides.html' -or -name introduction.html -or -name 'slides_ES.html' | xargs ./bin/filter-resource-metadata video | sort) <(git diff ${PREVIOUS_COMMIT_ID} --name-only | sort))"
else
changed_slides="$(find topics -name 'slides.html' -or -name introduction.html -or -name 'slides_ES.html' | xargs ./bin/filter-has-videos)"
changed_slides="$(find topics -name 'slides.html' -or -name introduction.html -or -name 'slides_ES.html' | xargs ./bin/filter-resource-metadata video)"
fi

$(npm bin)/http-server -p 9876 _site &
Expand Down
10 changes: 0 additions & 10 deletions bin/filter-has-videos

This file was deleted.

Loading

0 comments on commit 727f741

Please sign in to comment.