Created
April 26, 2016 02:59
-
-
Save JoshCheek/181663ae1324bd6a0e0024ccf3afb023 to your computer and use it in GitHub Desktop.
FCC challenges
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'nokogiri' | |
require 'rest-client' | |
require 'erb' | |
def get_html(url, filename) | |
one_hour = 1*60*60 | |
stale_time = Time.now - one_hour | |
if File.exist?(filename) && stale_time < File.stat(filename).mtime | |
File.read filename | |
else | |
$stderr.puts "Fetching #{url}" | |
raw_html = RestClient.get url | |
File.write filename, raw_html | |
raw_html | |
end | |
end | |
def traverse(element, &callback) | |
element.children.each do |child| | |
callback.call(child) | |
traverse child, &callback | |
end | |
end | |
def get_programs(doc) | |
programs = {} | |
current_program = nil | |
challenge_set = nil | |
traverse doc.at_css('.map-accordion') do |element| | |
case element.name | |
when 'h2' | |
text = element.text.gsub(/\W+/, ' ').strip | |
current_program = {} | |
programs[text] = current_program | |
when 'h3' | |
text = element.text.gsub(/\W+/, ' ').strip | |
challenge_set = [] | |
current_program[text] = challenge_set | |
when 'p' | |
classes = element['class'].split | |
if classes.include?('challenge-title') && !classes.include?('disabled') | |
# there is a: element['name'] | |
# but it doesn't seem to be used | |
text = element.at_css('a').children[0].text | |
challenge_set << text | |
end | |
end | |
end | |
programs | |
end | |
def get_completions(doc, corrections) | |
completions = [] | |
traverse doc.at_css('body') do |element| | |
next unless element.name == 'tr' | |
# <tr> | |
# <td class="col-xs-5 hidden-xs">Reverse a String</td> | |
# <td class="col-xs-2 hidden-xs">Apr 08, 2016</td> | |
# <td class="col-xs-2 hidden-xs">Apr 08, 2016</td> | |
# <td class="col-xs-2 hidden-xs"> | |
# <a href="/challenges/Reverse a String?solution=function%20reverseString(str)%20%7B%0A%20%20var%20s%20%3D%20%22%22%3B%0A%20%20for(var%20i%3Dstr.length-1%3B%20i%20%3E%3D%200%3B%20--i)%0A%20%20%20%20s%20%2B%3D%20str%5Bi%5D%3B%0A%20%20%0A%20%20return%20s%3B%0A%7D%0A%0AreverseString(%22hello%22)%3B%0A" target="_blank">View solution</a> | |
# </td> | |
# <td class="col-xs-12 visible-xs"> | |
# <a href="/challenges/Reverse a String?solution=function%20reverseString(str)%20%7B%0A%20%20var%20s%20%3D%20%22%22%3B%0A%20%20for(var%20i%3Dstr.length-1%3B%20i%20%3E%3D%200%3B%20--i)%0A%20%20%20%20s%20%2B%3D%20str%5Bi%5D%3B%0A%20%20%0A%20%20return%20s%3B%0A%7D%0A%0AreverseString(%22hello%22)%3B%0A" target="_blank">Reverse a String</a> | |
# </td> | |
# </tr> | |
a = element.at_css('a') | |
next if !a # the row is a header | |
name = element.at_css('td').text | |
name = corrections.fetch(name, name) | |
complete = (a.text == 'View solution') | |
link = File.join("https://www.freecodecamp.com", a['href']) | |
completions << {name: name, link: link, complete: complete} | |
end | |
completions | |
end | |
def summary(programs, students, removed) | |
all_solved = students.flat_map { |_, s| s[:solutions] }.map { |s| s[:name] }.uniq | |
all_solutions = programs.values.map(&:values).flatten | |
unaccounted_for = all_solved - all_solutions - removed | |
html = <<-HTML | |
<!doctype html> | |
<html> | |
<head> | |
<title>Student FCC Summary</title> | |
<style> | |
body { | |
padding: 2em; | |
} | |
table { | |
border: 0.3em solid #888; | |
background-color: #DDD; | |
padding: 1em; | |
} | |
.program-name { | |
text-align: center; | |
background-color: #555; | |
color: #FFF; | |
font-weight: bold; | |
font-size: 1.2em; | |
} | |
.table-header { | |
background-color: #888; | |
color: #EEE; | |
} | |
.challenge-name { | |
text-align: center; | |
background-color: #AAA; | |
color: #FFF; | |
} | |
</style> | |
</head> | |
<body> | |
<% if unaccounted_for.any? %> | |
<h2>Unaccounted for</h2> | |
<ul class="unaccounted-for"> | |
<% unaccounted_for.each do |challenge| %> | |
<li><%= challenge %></li> | |
<% end %> | |
</ul> | |
<% end %> | |
<table> | |
<% programs.each do |program_name, challenge_sets| %> | |
<tr> | |
<td class="program-name" colspan="<%=h students.length+1 %>"><%=h program_name %></td> | |
</tr> | |
<% challenge_sets.each do |challenge_name, challenges| %> | |
<tr class="table-header"> | |
<th>Challenge</td> | |
<% students.each do |name, student| %> | |
<th><%=h name.capitalize %></td> | |
<% end %> | |
</tr> | |
<tr> | |
<td class="challenge-name" colspan="<%=h students.length+1 %>"><%=h challenge_name %></td> | |
</tr> | |
<% challenges.each do |challenge| %> | |
<tr> | |
<td><%=h challenge %></td> | |
<% students.each do |student_name, student_data| %> | |
<td> | |
<% solution = student_data[:solutions].find { |solution| solution[:name] == challenge } %> | |
<% if solution && solution[:complete] %> | |
<a href="<%=h solution[:link] %>">Complete</a> | |
<% elsif solution %> | |
<a href="<%=h solution[:link] %>">Incomplete</a> | |
<% else %> | |
Unattempted | |
<% end %> | |
</td> | |
<% end %> | |
</tr> | |
<% end %> | |
<% end %> | |
<% end %> | |
</table> | |
</body> | |
</html> | |
HTML | |
ERB.new(html).result(binding) | |
end | |
# the cache dir | |
Dir.mkdir 'cache' unless Dir.exist?('cache') | |
# get the challenges | |
doc = Nokogiri::HTML(get_html('https://www.freecodecamp.com/map', 'cache/fcc_map.html')) | |
programs = get_programs(doc) | |
# get each student's progress | |
students = { | |
javi: { | |
username: 'Javi-Rev', | |
solutions: [], | |
}, | |
sean: { | |
username: 'SeanGallen', | |
solutions: [], | |
}, | |
matthew: { | |
username: 'MatthewSwan', | |
solutions: [], | |
}, | |
dongmin: { | |
username: 'medi86', | |
solutions: [], | |
}, | |
james: { | |
username: 'JamesMarkWilton', | |
solutions: [], | |
}, | |
megan: { | |
username: 'darkstarre', | |
solutions: [], | |
}, | |
greg: { | |
username: '101glover', | |
solutions: [], | |
}, | |
vince: { | |
username: 'Vince331', | |
solutions: [], | |
}, | |
derreck: { | |
username: 'DerreckM', | |
solutions: [], | |
}, | |
josh: { | |
username: 'JoshCheek', | |
solutions: [], | |
}, | |
} | |
corrections = { | |
'Use Bracket Notation to Find the NthtoLast Character in a String' => 'Use Bracket Notation to Find the Nth-to-Last Character in a String', | |
'Access MultiDimensional Arrays With Indexes' => 'Access Multi-Dimensional Arrays With Indexes', | |
'Manipulate Arrays With push' => 'Manipulate Arrays With push()', | |
'Manipulate Arrays With pop' => 'Manipulate Arrays With pop()', | |
'Manipulate Arrays With shift' => 'Manipulate Arrays With shift()', | |
'Manipulate Arrays With unshift' => 'Manipulate Arrays With unshift()', | |
'Global vs Local Scope in Functions' => 'Global vs. Local Scope in Functions', | |
'Introducing JavaScript Object Notation JSON' => 'Introducing JavaScript Object Notation (JSON)', | |
'Iterate over Arrays with map' => 'Iterate over Arrays with .map', | |
'Condense arrays with reduce' => 'Condense arrays with .reduce', | |
'Filter Arrays with filter' => 'Filter Arrays with .filter', | |
'Sort Arrays with sort' => 'Sort Arrays with .sort', | |
'Concatenate Arrays with concat' => 'Reverse Arrays with .reverse', | |
'Split Strings with split' => 'Concatenate Arrays with .concat', | |
'Join Strings with join' => 'Split Strings with .split', | |
'Reverse Arrays with reverse' => 'Join Strings with .join', | |
'Concatenate Strings with concat' => 'Concatenate Arrays with .concat', | |
} | |
removed = [ | |
'Use RGB to Color Elements Gray', | |
'Browse Camper News', | |
'Reference our Wiki', | |
'Perform Arithmetic Operations on Decimals with JavaScript', | |
'Create a JavaScript Slot Machine', | |
'Add your JavaScript Slot Machine Slots', | |
'Bring your JavaScript Slot Machine to Life', | |
'Give your JavaScript Slot Machine some Stylish Images', | |
'Watch us Code Live on Twitchtv', | |
'Meet Bonfire', | |
] | |
students.each do |name, student| | |
raw_html = get_html("https://www.freecodecamp.com/#{student[:username]}", "cache/#{name}.html") | |
doc = Nokogiri::HTML(raw_html) | |
student[:solutions].concat(get_completions(doc, corrections)) | |
end | |
def h(text) | |
ERB::Util.html_escape(text) | |
end | |
filename = 'result.html' | |
File.write filename, summary(programs, students, removed) | |
$stderr.puts "Result saved in #{filename} ($ open #{filename})" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-DerreckM
+fcc809bd3b7