Skip to content

Instantly share code, notes, and snippets.

@karmi
Last active January 9, 2016 18:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save karmi/61fac7da4c29f712913b to your computer and use it in GitHub Desktop.
Save karmi/61fac7da4c29f712913b to your computer and use it in GitHub Desktop.
NYT ISIS Attacks Chart
[
{
"date" : "2015-12-02",
"fatalities" : 14,
"location" : "California",
"region" : "North America",
"description" : "Shooting",
"link" : "http://www.nytimes.com/interactive/2015/12/02/us/california-mass-shooting-san-bernardino.html"
},
{
"date" : "2015-11-26",
"fatalities" : 1,
"location" : "Bangladesh",
"region" : "Asia",
"description" : "Attack on mosque",
"link" : "http://www.nytimes.com/2015/11/27/world/asia/gunmen-strike-shiite-mosque-in-bangladesh.html"
},
{
"date" : "2015-11-24",
"fatalities" : 7,
"location" : "Egypt",
"region" : "Middle East",
"description" : "Attack on hotel",
"link" : null
},
{
"date" : "2015-11-24",
"fatalities" : 12,
"location" : "Tunisia",
"region" : "Middle East",
"description" : "Suicide bomb",
"link" : "http://www.nytimes.com/2015/11/26/world/middleeast/tunisia-suicide-bombing-presidenital-guard.html"
},
{
"date" : "2015-11-13",
"fatalities" : 130,
"location" : "Paris",
"region" : "Europe",
"description" : "Attacks on club and restaurants",
"link" : "http://www.nytimes.com/2015/11/15/world/europe/paris-terrorist-attacks.html"
},
{
"date" : "2015-11-12",
"fatalities" : 43,
"location" : "Lebanon",
"region" : "Middle East",
"description" : "Suicide bombing",
"link" : "http://www.nytimes.com/2015/11/13/world/middleeast/lebanon-explosions-southern-beirut-hezbollah.html"
},
{
"date" : "2015-11-04",
"fatalities" : 4,
"location" : "Egypt",
"region" : "Middle East",
"description" : "Suicide bombing",
"link" : "http://www.nytimes.com/2015/11/05/world/middleeast/egypt-bombing-sinai-police.html"
},
{
"date" : "2015-11-04",
"fatalities" : 1,
"location" : "Bangladesh",
"region" : "Asia",
"description" : "Stabbing and shooting",
"link" : "http://www.nytimes.com/2015/11/06/world/asia/isis-bangladesh-police-attack.html"
},
{
"date" : "2015-10-31",
"fatalities" : 224,
"location" : "Egypt",
"region" : "Middle East",
"description" : "Russian plane downed",
"link" : "http://www.nytimes.com/2015/11/10/world/middleeast/suspects-in-russian-jet-crash-risk-exposing-their-isis-allies-to-a-backlash.html"
},
{
"date" : "2015-10-24",
"fatalities" : 1,
"location" : "Bangladesh",
"region" : "Asia",
"description" : "Bombings",
"link" : "http://www.nytimes.com/2015/10/25/world/asia/isis-claims-responsibility-for-attack-in-bangladesh-targeting-shias.html"
},
{
"date" : "2015-10-10",
"fatalities" : 102,
"location" : "Ankara",
"region" : "Middle East",
"description" : "Bombing of a peace rally",
"link" : "http://www.nytimes.com/2015/10/11/world/europe/ankara-turkey-explosion-deaths.html"
},
{
"date" : "2015-10-06",
"fatalities" : 25,
"location" : "Yemen",
"region" : "Middle East",
"description" : "Bombings",
"link" : "http://www.nytimes.com/2015/10/07/world/middleeast/yemen-aden-hotel-explosion.html"
},
{
"date" : "2015-10-03",
"fatalities" : 1,
"location" : "Bangladesh",
"region" : "Asia",
"description" : "Shooting",
"link" : "http://www.nytimes.com/2015/10/04/world/asia/in-bangladesh-a-second-foreigner-is-violently-killed.html"
},
{
"date" : "2015-09-28",
"fatalities" : 1,
"location" : "Bangladesh",
"region" : "Asia",
"description" : "Shooting",
"link" : "http://www.nytimes.com/2015/09/30/world/asia/-isis-bangladesh-cesare-tavella.html"
},
{
"date" : "2015-09-24",
"fatalities" : 25,
"location" : "Yemen",
"region" : "Middle East",
"description" : "Bombings",
"link" : "http://www.nytimes.com/2015/09/25/world/middleeast/sana-yemen-mosque-suicide-attack.html"
},
{
"date" : "2015-09-02",
"fatalities" : 20,
"location" : "Yemen",
"region" : "Middle East",
"description" : "Bombings",
"link" : "http://www.nytimes.com/2015/09/03/world/middleeast/yemen-red-cross-workers-shot-dead.html"
},
{
"date" : "2015-08-26",
"fatalities" : 2,
"location" : "Egypt",
"region" : "Middle East",
"description" : "Shooting of police officers",
"link" : null
},
{
"date" : "2015-08-12",
"fatalities" : 1,
"location" : "Egypt",
"region" : "Middle East",
"description" : "Beheading of Croatian worker",
"link" : "http://www.nytimes.com/2015/08/13/world/middleeast/isis-egypt-croatian-beheading.html"
},
{
"date" : "2015-08-07",
"fatalities" : 15,
"location" : "Saudi Arabia",
"region" : "Middle East",
"description" : "Sucide bombing at a mosque",
"link" : "http://www.nytimes.com/2015/08/07/world/middleeast/suicide-bombing-saudi-arabia.html"
},
{
"date" : "2015-07-20",
"fatalities" : 32,
"location" : "Turkey",
"region" : "Middle East",
"description" : "Suicide bombing",
"link" : "http://www.nytimes.com/2015/07/23/world/europe/turkey-suruc-bombing.html"
},
{
"date" : "2015-07-11",
"fatalities" : 1,
"location" : "Egypt",
"region" : "Middle East",
"description" : "Embassy explosion",
"link" : "http://www.nytimes.com/2015/07/12/world/middleeast/egypt-bombing-at-italian-consulate-in-cairo.html"
},
{
"date" : "2015-07-01",
"fatalities" : 21,
"location" : "Egypt",
"region" : "Middle East",
"description" : "Attacks on military bases",
"link" : "http://www.nytimes.com/2015/07/02/world/middleeast/sinai-isis-attack.html"
},
{
"date" : "2015-06-26",
"fatalities" : 38,
"location" : "Tunisia",
"region" : "Middle East",
"description" : "Shooting at beach resort",
"link" : "http://www.nytimes.com/2015/06/27/world/africa/gunmen-attack-hotel-in-sousse-tunisia.html"
},
{
"date" : "2015-06-26",
"fatalities" : 27,
"location" : "Kuwait",
"region" : "Middle East",
"description" : "Suicide bombing at a mosque",
"link" : "http://www.nytimes.com/2015/06/27/world/middleeast/terror-attacks-france-tunisia-kuwait.html"
},
{
"date" : "2015-06-26",
"fatalities" : 70,
"location" : "Somalia",
"region" : "Africa",
"description" : "Attack on African Union base",
"link" : "http://www.nytimes.com/2015/06/27/world/africa/somalia-shabab-militants-attack-african-union-base.html"
},
{
"date" : "2015-06-17",
"fatalities" : 30,
"location" : "Yemen",
"region" : "Middle East",
"description" : "Car bombings",
"link" : "http://www.nytimes.com/2015/06/18/world/middleeast/isis-claims-responsibility-for-deadly-bombings-in-yemen.html"
},
{
"date" : "2015-06-05",
"fatalities" : 2,
"location" : "Turkey",
"region" : "Middle East",
"description" : "Explosion",
"link" : "http://www.nytimes.com/2015/06/06/world/europe/days-before-election-in-turkey-blasts-at-rally-kill-2.html"
},
{
"date" : "2015-06-03",
"fatalities" : 10,
"location" : "Afghanistan",
"region" : "Asia",
"description" : "Beheading of Taliban members",
"link" : "http://www.nytimes.com/2015/06/05/world/asia/afghanistan-taliban-face-insurgent-threat-from-isis.html"
},
{
"date" : "2015-05-31",
"fatalities" : 4,
"location" : "Libya",
"region" : "Middle East",
"description" : "Suicide bomber",
"link" : "http://www.nytimes.com/2015/06/01/world/africa/western-officials-alarmed-as-islamic-state-expands-territory-in-libya.html"
},
{
"date" : "2015-05-29",
"fatalities" : 3,
"location" : "Saudi Arabia",
"region" : "Middle East",
"description" : "Suicide bombing at a mosque",
"link" : "http://www.nytimes.com/2015/05/30/world/middleeast/mosque-bombing-saudi-arabia-shiites-dammam.html"
},
{
"date" : "2015-05-22",
"fatalities" : 21,
"location" : "Saudi Arabia",
"region" : "Middle East",
"description" : "Suicide bombing at a mosque",
"link" : "http://www.nytimes.com/2015/05/23/world/middleeast/suicide-bombing-saudi-arabia-shiites-sunnis-yemen-mosque.html"
},
{
"date" : "2015-04-30",
"fatalities" : 15,
"location" : "Yemen",
"region" : "Middle East",
"description" : "Killing of soldiers",
"link" : null
},
{
"date" : "2015-04-19",
"fatalities" : 28,
"location" : "Libya",
"region" : "Middle East",
"description" : "Beheading and shooting",
"link" : ""
},
{
"date" : "2015-04-12",
"fatalities" : 12,
"location" : "Egypt",
"region" : "Middle East",
"description" : "Attack on police and military",
"link" : "http://www.nytimes.com/2015/04/13/world/middleeast/sinai-group-kills-officers-in-bombings.html"
},
{
"date" : "2015-04-12",
"fatalities" : 2,
"location" : "Libya",
"region" : "Middle East",
"description" : "Shooting at embassy",
"link" : "http://www.nytimes.com/2015/04/13/world/middleeast/fatal-shooting-at-south-korean-embassy-in-libya.html"
},
{
"date" : "2015-04-08",
"fatalities" : 2,
"location" : "Saudi Arabia",
"region" : "Middle East",
"description" : "Shooting",
"link" : null
},
{
"date" : "2015-04-05",
"fatalities" : 4,
"location" : "Libya",
"region" : "Middle East",
"description" : "Attack at security checkpoint",
"link" : null
},
{
"date" : "2015-04-02",
"fatalities" : 13,
"location" : "Egypt",
"region" : "Middle East",
"description" : "Car bombs",
"link" : "http://www.nytimes.com/2015/04/03/world/middleeast/egypt-militants-attack-sinai-checkpoints.html"
},
{
"date" : "2015-03-20",
"fatalities" : 130,
"location" : "Yemen",
"region" : "Middle East",
"description" : "Suicide bombing at a mosque",
"link" : "http://www.nytimes.com/2015/03/21/world/middleeast/suicide-attacks-at-shiite-mosques-in-yemen.html"
},
{
"date" : "2015-03-18",
"fatalities" : 22,
"location" : "Tunisia",
"region" : "Middle East",
"description" : "Shooting at a museum",
"link" : "http://www.nytimes.com/2015/03/20/world/africa/miltants-isis-included-claim-tunisia-museum-attack.html"
},
{
"date" : "2015-02-20",
"fatalities" : 40,
"location" : "Libya",
"region" : "Middle East",
"description" : "Car bombs",
"link" : "http://www.nytimes.com/2015/02/21/world/middleeast/militants-claiming-isis-ties-say-they-carried-out-libya-bombings.html"
},
{
"date" : "2015-02-15",
"fatalities" : 21,
"location" : "Libya",
"region" : "Middle East",
"description" : "Beheading of Egyptian Christians",
"link" : "http://www.nytimes.com/2015/02/16/world/middleeast/islamic-state-video-beheadings-of-21-egyptian-christians.html"
},
{
"date" : "2015-02-15",
"fatalities" : 2,
"location" : "Denmark",
"region" : "Europe",
"description" : "Shooting",
"link" : "http://www.nytimes.com/2015/02/16/world/europe/copenhagen-attacks-suspect-is-killed-police-say.html"
},
{
"date" : "2015-02-03",
"fatalities" : 12,
"location" : "Libya",
"region" : "Middle East",
"description" : "Attack on air field",
"link" : "http://www.nytimes.com/2015/02/05/world/middleeast/people-are-killed-in-attack-on-libyan-oil-field.html"
},
{
"date" : "2015-01-29",
"fatalities" : 44,
"location" : "Egypt",
"region" : "Middle East",
"description" : "Bombings",
"link" : "http://www.nytimes.com/2015/01/30/world/middleeast/bombings-of-security-facilities-in-sinai-kill-at-least-26.html"
},
{
"date" : "2015-01-27",
"fatalities" : 8,
"location" : "Libya",
"region" : "Middle East",
"description" : "Armed assualt on Hotel",
"link" : "http://www.nytimes.com/2015/01/28/world/middleeast/islamic-state-tripoli-libya-terror-attack.html"
},
{
"date" : "2015-01-07",
"fatalities" : 12,
"location" : "France",
"region" : "Europe",
"description" : "Shootings",
"link" : "http://www.nytimes.com/2015/01/08/world/europe/charlie-hebdo-paris-shooting.html"
},
{
"date" : "2015-01-09",
"fatalities" : 5,
"location" : "France",
"region" : "Europe",
"description" : "Shootings",
"link" : "http://www.nytimes.com/2015/03/14/world/europe/kosher-supermarket-attacked-in-paris-to-reopen.html"
}
]
<!DOCTYPE html>
<html>
<head>
<title>NYT ISIS Attacks Chart</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<style>
body {
font-family: 'Helvetica Neue', sans-serif;
padding: 0;
margin: 40px;
}
#chart {
position: relative;
}
#chart circle {
fill: orange;
stroke: #bf3e05;
stroke-width: 2px;
opacity: 0.7;
}
#chart circle.hidden {
display: none;
}
#chart circle:hover {
fill: #bf3e05;
opacity: 1;
}
#chart circle.has_link:hover {
cursor: pointer;
}
.x.axis path.domain {
display: none;
}
.x.axis line {
stroke: #c1c1c1;
shape-rendering: crispEdges;
}
.x.axis text {
fill: #999;
font-weight: 100;
}
div.legend {
color: #444;
background-color: #fff;
position: absolute;
display: none;
border-left: 1px solid #bf3e05;
z-index: -1;
padding: 10px 0px 1px 10px;
}
div.legend.visible {
display: block;
}
div.legend p {
font-size: 80%;
padding: 0;
margin: 0 0 0.25em 0;
}
div.legend p:last-child {
padding-top: 0.28em;
margin-bottom: -0.20em;
}
div.legend small {
color: #878787;
font-size: 70%;
font-weight: 200;
text-transform: uppercase;
}
div.legend .fatalities {
font-size: 140%;
}
div.legend .link {
color: #666;
font-size: 65%;
}
.region-switch {
position: absolute;
top: 1.75em;
right: 40px;
}
a.switch {
color: #444;
background: #ccc;
font-size: 80%;
text-decoration: none;
margin: 0 0.25em 0.2em 0;
padding: 0.25em 0.5em 0.25em 0.5em;
border-radius: 0.4em;
display: block;
}
a.switch.active {
color: #ccc;
background: #444;
}
div.total {
color: #666;
font-size: 80%;
text-transform: uppercase;
font-weight: 100;
letter-spacing: -0.05em;
position: absolute;
top: 0;
right: 40px;
}
div.total p {
margin: 0 0.45em 0 0;
padding: 0;
}
div.total strong {
font-size: 140%;
font-weight: 900;
padding-right: 0.1em;
}
.footer {
color: #bfbfbf;
font-size: 65%;
font-weight: 200;
text-align: left;
position: absolute;
bottom: 0em;
right: 42px;
width: 8.75em;
}
.footer small:after { content:"\A"; white-space:pre; } {
}
.footer p {
margin: 0; padding: 0;
}
.footer a {
color: #bfbfbf;
}
</style>
</head>
<body>
<div id="chart"></div>
<script>
d3.json('data.json', function(error, json) {
var data = json.sort(function(a, b) { return b.fatalities - a.fatalities });
var width = 1200,
height = 200,
margin = { top: 0, right: 100, bottom: 0, left: 50 };
var svg = d3.select('#chart')
.style({ width: (width+margin.right) + 'px' })
.append('svg')
.attr('width', width)
.attr('height', height);
var xScale = d3.time.scale();
var rScale = d3.scale.sqrt();
var xAxis = d3.svg.axis();
xScale
.domain( d3.extent( json, function(d) { return d3.time.format('%Y-%m-%d').parse(d.date) } ) )
.range([margin.left, width-margin.right])
rScale
.domain( [0, d3.max(json, function(d) { return d.fatalities })] )
.range([0, height/3])
xAxis.scale(xScale);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height-25) + ")")
.call(xAxis);
svg.selectAll('.x.axis line')
.attr('y1', -height)
.attr('y2', 0)
var circle = svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('id', function(d, i) { return ('circle-'+i) })
.attr('cx', function(d) { return xScale(d3.time.format('%Y-%m-%d').parse(d.date)) })
.attr('cy', height/2)
.attr('r', function(d) { return rScale(d.fatalities) })
.attr('class', function(d, i) {
c = !!d.link ? 'has_link' : ''
c += !!d.region ? (' ' + d.region.toLowerCase().replace(/\s/, '-')) : ''
return c
})
.on('mouseover', function(d, i) {
d3.select('#legend-'+i).classed('visible', true)
d3.select('.x.axis').style('opacity', '0.5')
})
.on('mouseout', function(d, i) {
d3.select('#legend-'+i).classed('visible', false)
d3.select('.x.axis').style('opacity', '1')
})
.on('click', function(d, i) {
return !!d.link ? window.open(d.link, '_blank') : null
})
.append('title')
.text(function(d) { return d.link ? 'Click for more info' : '' });
var legend = d3.select('#chart').selectAll('div.legend')
.data(data)
.enter()
.append('div')
.attr('id', function(d, i) { return ('legend-'+i) })
.attr('class', 'legend')
.attr('style', function(d) {
return 'top: ' + ((height/2)) + 'px; left:' + xScale(d3.time.format('%Y-%m-%d').parse(d.date)) + 'px;' + 'padding-top: ' + ((height/2)+20) + 'px'
})
.html(function(d) {
var format = d3.time.format('%B %d, %Y');
html = '<p class="fatalities"><strong>' + d.fatalities + '</strong> <small>fatalities</small></p> ' +
'<p class="description">' + d.description + '</p>' +
'<p class="location"><small>Location </small>' + d.location + '</p> ' +
'<p class="date"><small>Date </small>' + format(d3.time.format('%Y-%m-%d').parse(d.date)) + '</p> ';
if ( d.link ) {
html += '<p class="link">' + d.link + '</p>';
}
return html
});
d3.select('#chart')
.insert('div', ':first-child')
.attr('class', 'region-switch')
.selectAll('a.switch')
.data(['All', 'Middle East', 'Africa', 'Europe', 'North America', 'Asia'])
.enter()
.append('a')
.attr('href', function(d) { return '#' + d.toLowerCase().replace(/\s/, '-') })
.attr('class', function(d) { return 'switch ' + d.toLowerCase().replace(/\s/, '-') })
.text(function(d) { return d })
.on('click', function(d) {
if ( 'All' === d ) {
svg.selectAll('circle')
.transition().duration(150)
.style('opacity', 0.7)
.each('end', function() { d3.select(this).classed('hidden', false) })
d3.select('#chart .total strong').text( d3.sum(data, function(d) { return d.fatalities }) )
} else {
svg.selectAll('circle')
.filter(function(e) { return e.region != d })
.transition().duration(150)
.style('opacity', 0)
.each('end', function() { d3.select(this).classed('hidden', true) })
svg.selectAll('circle')
.filter(function(e) { return e.region == d })
.transition().duration(150)
.style('opacity', 0.7)
.each('end', function() { console.log(d3.select(this)); d3.select(this).classed('hidden', false) })
d3.select('#chart .total strong').text( d3.sum(data.filter(function(e) { return e.region == d }), function(e) { return e.fatalities }) )
}
d3.selectAll('a.switch').classed('active', false)
d3.select(this).classed('active', true)
});
d3.select('a.switch.all').classed('active', true);
var total = d3.select('#chart')
.insert('div', ':first-child')
.datum(d3.sum(data, function(d) { return d.fatalities }))
.attr('class', 'total')
.html(function(d) {
return '<p><strong>' + d + '</strong> fatalities</p>'
});
d3.select('#chart')
.append('div')
.attr('class', 'footer')
.html('<p><small>Data sources</small> <a href="http://www.nytimes.com/interactive/2015/06/17/world/middleeast/map-isis-attacks-around-the-world.html?_r=3">NYT</a>, <a href="https://en.wikipedia.org/wiki/List_of_Islamist_terrorist_attacks#2015.E2.80.93present">Wikipedia</a></p>');
if (document.location.hash.length > 0) {
var path = d3.select('.' + document.location.hash.replace('#', ''))
path.on('click').call(path.node(), path.datum())
};
});
</script>
<script>
// Make the canvas wider on http://bl.ocks.org
if (document.location.hostname.indexOf("bl.ocks.org") > -1) {
d3.select(parent.document).select('iframe').style('width', '1325px').style('height', '400px');
d3.select(parent.document).select('body').style('width', '1325px').style('margin', '2em');
d3.select(parent.document).select('.gist-source pre').style('border-left', 'none');
}
</script>
</body>
</html>
task :default => :server
# Serving files via Rack
desc "Serve the files via Rack and Thin"
task :server => [:dependencies] do
port = ENV['PORT'] || 8000
puts "Launching local webserver at <http://localhost:#{port}> ...", "-"*80
begin
Thin::Logging.silent = true
Thin::Server.start port, lambda { |env|
if env['PATH_INFO'] == '/'
[200, {'Content-Type' => 'text/html'}, File.new('index.html')]
else
Rack::Directory.new('.').(env)
end
}
rescue Exception => e
if e.message =~ /no acceptor/
puts "[!] Port #{port} not available, trying next one...", ""
port += 1
puts "Launching local webserver at <http://localhost:#{port}> ...", "-"*80
retry
end
raise
end
end
task :dependencies do
%w| rack thin |.each do |lib|
begin
require lib
rescue LoadError
puts "[!] Required gem '#{lib}' not found, please install it with:", "",
" $ gem install #{lib}", "",
"-"*80
raise
end
end
end
# Stepping through the tutorial
begin
require 'term/ansicolor'
class String
include Term::ANSIColor
end
rescue LoadError
class String
def black; self; end
def on_green; self; end
def on_yellow; self; end
end
end
_ = '-'*80
desc "List the steps of this tutorial"
task :steps do
puts _, "Steps of this tutorial", _
current = GitSteps.current_step
GitSteps.steps.each_with_index do |step, i|
is_current = step.sha == current.sha ? "*" : " "
puts "#{is_current} #{(i+1).to_s.rjust(3).bold} | #{step.subject.gsub(/^\[\d+\] /, '')}"
end; puts
end
desc "Display current step"
task :step => [:check] do
step = GitSteps.current_step
puts _, "#{step.sha} | #{step.subject.bold}",
_, "#{step.body}"
end
desc "Start the tutorial or switch back to first step"
task :start => [:check] do
puts _, "Starting the tutorial with the first step".white.on_magenta, _
step = GitSteps.steps.first
puts "#{step.sha} | #{step.subject.bold}",
"#{step.body}"
exec "git checkout #{step.sha} > /dev/null 2>&1 "
end
desc "Step to the next step of tutorial"
task :next => [:check] do
if step = GitSteps.next_step
puts _, "#{step.sha} | #{step.subject.bold}",
_, "#{step.body}"
exec "git checkout #{step.sha} > /dev/null 2>&1 "
else
puts "You have reached the end of the tutorial".white.on_green,
"Start again with: rake start"
end
end
desc "Step to the previous step of tutorial"
task :previous => [:check] do
if step = GitSteps.previous_step
puts _, "#{step.sha} | #{step.subject.bold}",
_, "#{step.body}"
exec "git checkout #{step.sha} > /dev/null 2>&1 "
else
puts "You are at the beginning of the tutorial".white.on_green,
"Follow the tutorial with: rake next"
end
end
desc "Show differences between the current step and the previous step"
task :diff do
current = GitSteps.current_step
previous = GitSteps.previous_step
(puts "[!] Cannot find previous step".white.on_red; exit(1)) unless previous
puts _, "Previous : #{previous.subject}",
"Current : #{current.subject}", _
exec "git diff --color --ignore-all-space --minimal HEAD^ HEAD | cat"
end
desc "Reset the tutorial and switch to master branch"
task :reset => [:check] do
exec "git checkout master > /dev/null"
end
task :check do
if `which git` == ''
puts "[!] ERROR: You need Git installed to step through the tutorial.".white.on_red
exit(1)
end
end
module GitSteps
class Commit
attr_reader :sha, :subject, :body
def initialize(commit)
@sha, @subject = commit.split('|||', 2)
@body = %x[git log -n 1 --format='%b' #{sha}].chomp
end
def step?; subject =~ /^\[\d+\]/; end
end
def steps
%x[git log --reverse --format='%h|||%s' master]
.chomp
.split("\n")
.map { |commit| Commit.new(commit) }
.select { |commit| commit.step? }
end
def current_step
Commit.new( %x[git log -n 1--reverse --format='%h|||%s'].chomp )
end
def next_step
steps.select { |step| step.subject > current_step.subject }.first
end
def previous_step
steps.select { |step| step.subject < current_step.subject }.last
end
extend self
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment