Rake 201

In dit tweede artikel over Rake duiken we een beetje dieper en behandelen enigszins geavanceerde onderwerpen zoals bestandstaken, regels, multitasks en meer die je harkkrakers aanzienlijk zullen verbeteren.

Onderwerpen

  • ontkenning
  • Standaardtaak
  • Het taakobject
  • Bestandsopdrachten
  • Directory Methode
  • Bestand hulpprogramma's
  • Reglement
  • Spoor Vlag
  • Parallelle taken

ontkenning

Ik wil dit onderwerp vanuit een meer algemeen oogpunt benaderen. Dit is geen artikel dat je een lijst met Rake-taken laat zien met slimme oplossingen die klaar zijn om te worden gekopieerd en geplakt zonder veel bijzaak. Het is meer de bedoeling om een ​​kijkje onder de motorkap te nemen, terwijl het nieuw is voor beginners en ook interessant voor mensen die nog niet zoveel met Rake hebben gespeeld, naast de voor de hand liggende Rake-taken in Rails. 

Het is het begrip van de tool en wat het biedt dat een hoger rendement oplevert, denk ik. Ik hoop dat je het niet erg vindt. Voor mij is het bedekken van de principes waardevoller - en benaderbaarder voor beginners - en het is aan jou om te doen wat je ermee doet in je eigen applicaties.

Standaardtaak

Ik weet zeker dat je de term op zijn minst ergens eerder hebt gehoord. Maar wat is een standaardtaak eigenlijk? Het is niets magisch, maar laten we dit snel uit de weg ruimen. Wanneer je rent hark zonder een extra naam voor een harktaak, wordt de standaardtaak uitgevoerd.

schelp

hark

Wat Rakefile

desc 'Dit is de standaardtaak. Geen argumenten nodig 'task: default do puts' Een taak die u misschien regelmatig wilt uitvoeren 'einde

In Rails wordt verondersteld dat de standaardtaak uw tests uitvoert. Uw gok is net zo goed als die van mij, maar ik veronderstel dat dit het resultaat was van tests die vaker moesten worden uitgevoerd dan welke andere taak dan ook. Wanneer u de waarde opnieuw definieert standaard Harktaak in Rails, het komt gewoon overeen met de taak die door Rails is gedefinieerd - hij zal het niet opnieuw definiëren. Het is eigenlijk hoe Rake werkt. Wanneer u een Rake-taak opnieuw definieert, telt u de vorige definities op.

Bestandsopdrachten

Zoals de naam doet vermoeden, zijn dit taken die u uitvoert op bestanden (en mappen). Ze hebben echter een paar trucjes voor de boeg. Rake zal natuurlijk veel met bestanden werken. Geen verrassing dat iemand dat patroon herkende en gespecialiseerde bestandstaken voor uw gemak creëerde - vooral om de eenvoudige reden om duplicatie of verspilling van verwerkingsmogelijkheden te voorkomen.

Het omzetten van bestanden van het ene type naar het andere is een veel voorkomende taak. De bronnen zijn uw afhankelijkheden en de taaknamen zijn wat volgt op de het dossier trefwoord. Markdown naar HTML-bestanden converteren, HTML-bestanden converteren naar e-bookformaten, JPG-afbeeldingen naar PNG-afbeeldingen, compilatie van broncode, het bouwen van statische pagina's of het wijzigen van bestandsextensies en nog veel meer opties staan ​​tot uw beschikking. We zouden dit allemaal handmatig kunnen doen, maar dit is natuurlijk vervelend en ineffectief. Het schrijven van code hiervoor is veel eleganter en schaalbaarder.

Het gebruik van bestandstaken verschilt niet veel van "gewone" taken. Ze worden ook weergegeven als u via Router een lijst met Rake-taken aanvraagt hark -T. In feite behandelt Rake alle taken op dezelfde manier, behalve multitasken een beetje. Het toevoegen van beschrijvingen en voorwaarden is ook geen probleem voor bestandstaken om te verwerken. 

Eigenlijk zijn voorwaarden een noodzaak om bronbestanden te vermelden voordat ze worden verwerkt. We hebben de bron nodig om te kunnen werken, wat logisch is als een afhankelijkheid, natuurlijk. Zonder dit zou Rake niet weten hoe het verder moest gaan - het kan immers niet uit het niets het nieuwe bestand creëren.

Wat Rakefile

bestand 'mi6 / q / gadgets / secret_list.md' => 'mi6 / research / secret_list.md' do cp 'mi6 / research / secret_list.md', 'mi6 / q / gadgets / secret_list.md' end

De naam voor uw bestandstaak is eigenlijk uw doelbestand, het bestand dat u wilt maken. De vereiste is het bronbestand dat voor de taak nodig is. In het blok vertel je Rake hoe je de gewenste uitvoer maakt - hoe je het bouwt met behulp van de vereiste bestand (en) die al bestaan. Invoer uitvoer. Dit kan bijvoorbeeld een shell-opdracht zijn met behulp van de pandoc tool die Markdown-bestanden omzet in HTML-bestanden. De toepassingen voor bestandstaken zijn meer dan voldoende. De syntaxis kan echter in het begin een beetje raar aanvoelen. ik snap het.

Rake controleert eerst of het doelbestand bestaat en, zo ja, controleert het of het tijdstempel ouder is dan de vereiste bestanden - een tijdsgebonden afhankelijkheid. Rake voert de bestandstaak uit als de tijdstempel ouder is dan de vereisten of als het bestand nog niet bestaat. Dat is erg handig als u meer dan een paar bestanden moet afhandelen, wat vooral cool is omdat u niet veel bestanden opnieuw hoeft te maken omdat u bijvoorbeeld een enkele in een verzameling hebt gewijzigd. In tegenstelling tot dat, worden gewone Rake-taken altijd uitgevoerd - ze controleren geen tijdstempels of andere wijzigingen, tenzij je ze zo maakt, natuurlijk.

Bestand hulpprogramma's

Wat Rakefile

desc 'Wijzig enkele bestandsextensie' bestand 'some_file.new_extension' => 'some_file.old_extension' do mv 'some_file.old_extension', 'some_file.new_extension' einde

schelp

$ rake some_file.new_extension => mv some_file.old_extension some_file.new_extension

In het geval u zich afvraagt ​​over de cp methode in het vorige voorbeeld of het bovenstaande mv opdracht, laten we het hebben over bestandshulpprogramma's. We hadden het kunnen gebruiken sh mv ... om een ​​Shell-opdracht uit te voeren vanuit een Rake-taak. Gelukkig voor ons kunnen we een module gebruiken die ervoor zorgt dat Shell dit soort dingen veel minder breed en platformonafhankelijk doet. fileutils is een Ruby-module met veel unixy-commando's voor bestandsbewerkingen: 

  • rm 
  • cp 
  • mv
  • mkdir
  • enzovoorts…

Als het wiel opnieuw uitvinden niet uw ding is, zal FileUtils een nuttige metgezel zijn die zich bezighoudt met bestanden. Vaak is Rake alles wat je nodig hebt, maar af en toe zul je heel blij zijn dat deze handige module je de rug heeft toegekeerd. RakeUtils deze module iets uitgebreid voor uw gemak. 

Laten we een lijst bekijken van wat tot uw beschikking staat en vervolgens inzoomen op een paar specifieke die voor u van belang kunnen zijn:

cd (richt, opties) cd (richt, opties) | dir | ... pwd () mkdir (richt, opties) mkdir (lijst, opties) mkdir_p (richt, opties) mkdir_p (lijst, opties) rmdir (richt, opties ) rmdir (lijst, opties) ln (oud, nieuw, opties) ln (lijst, destdir, opties) ln_s (oud, nieuw, opties) ln_s (lijst, destdir, opties) ln_sf (src, dest, options) cp (src , dest, opties) cp (lijst, richt, opties) cp_r (src, dest, opties) cp_r (lijst, richt, opties) mv (src, dest, opties) mv (lijst, richt, opties) rm (lijst, opties ) rm_r (lijst, opties) rm_rf (lijst, opties) installeren (src, dest, mode = , opties) chmod (modus, lijst, opties) chmod_R (modus, lijst, opties) chown (gebruiker, groep, lijst, opties) chown_R (gebruiker, groep, lijst, opties) touch (lijst, opties)

Hoewel ik aanneem dat je een newbie bent, ga ik er ook van uit dat je al eerder met Rails hebt gespeeld en dat je de erg simpele Unix-hulpprogramma's kent, zoals mv, CD, pwd, mkdir en dingen. Zo niet, doe je huiswerk en kom terug. 

In uw Rakefiles kunt u deze methoden direct uit de verpakking gebruiken. En om misverstanden te voorkomen, is dit een Ruby-laag die deze Unix-commando's 'imiteert' en die u kunt gebruiken in uw Rakefiles zonder voorvoegsels zoals sh-voor het uitvoeren van een Shell-opdracht. Trouwens, de opties je ziet in de bovenstaande lijst een hash van opties. Laten we een paar interessante opdrachten bekijken die van pas kunnen komen bij het schrijven van bestandstaken:

  • sh

Hiermee kun je shell-opdrachten uitvoeren vanuit je Ruby-bestanden.

  • CD

Dit is een heel eenvoudige, maar er is iets cools aan deze opdracht. Als je geeft CD met een blok, het verandert de huidige map naar zijn bestemming, doet zijn zaken zoals gedefinieerd in het blok, en keert dan terug naar de vorige werkdirectory om verder te gaan. Nette, eigenlijk!

  • cp_r

Hiermee kunt u bestanden en mappen recursief in bulk kopiëren.

  • mkdir_p

Maakt een doelmap en alle opgegeven ouders. Gelukkig voor ons hebben we de directory methode in Rake, wat nog handiger is, en daarom hebben we het niet nodig.

  • aanraken

Hiermee wordt de tijdstempel van een bestand bijgewerkt als dit bestaat - zo niet, dan wordt het gemaakt.

  • identiek?

Hiermee kunt u controleren of twee bestanden hetzelfde zijn.

Directory Methode

In Rake heb je een handige manier om directories te definiëren zonder te gebruiken mkdir of mkdir_p. Het is vooral handig wanneer u geneste mappen moet opbouwen. Een mappenboom kan lastig zijn als u een directorystructuur moet opbouwen via meerdere bestandstaken die tal van vereisten hebben voor de directorystructuur. Denk aan de directory methode als een maptaak.

Wat Rakefile

directory 'mi6 / q / special_gadgets'

Hierdoor ontstaan ​​de mappen in kwestie zonder veel gedoe. Wat misschien niet meteen voor de hand ligt, is dat je erop kunt vertrouwen als elke andere harktaak - als een vereiste. Zorg ervoor dat de naam van de bestandstaak, de naam, de map bevat waarvan u afhankelijk bent. Als er meerdere taken van afhankelijk zijn, wordt deze nog maar één keer gemaakt.

map 'mi6 / q / gadgets' desc 'Geheime onderzoeksgadgets overbrengen' bestand 'mi6 / q / gadgets / gadget_list.md' => 'mi6 / q / gadgets' do cp 'gadget_list.md', 'mi6 / q / special_gadgets /secret_gadget_list.md 'einde

Zoals je hier kunt zien, is Rake zeer consistent en denkt hij na over alle dingen die als taken kunnen worden gebouwd. Bedankt, Jim, dat maakt het leven gemakkelijk!

Reglement

Regels kunnen ons helpen om doublures te verminderen wanneer we ons bezighouden met taken-bestandstaken, eigenlijk. In plaats van Rake opdracht te geven om taken uit te voeren op bepaalde bestanden zoals somefile.markdown, we kunnen Rake leren om deze taken uit te voeren op een bepaald soort bestand, zoals een patroon of blauwdruk. Het omzetten van een reeks bestanden in plaats van enkele bestanden is een veel veelzijdiger en DROGE benadering. Taken als deze schalen veel beter wanneer we een patroon definiëren voor bestanden met vergelijkbare kenmerken.

Wat Rakefile

bestand "quartermaster_gadgets.html" => "quartermaster_gadgets.markdown" do sh "pandoc -s quartermaster_gadgets.markdown -o quartermaster_gadgets.html" einde

Zoals je kunt zien, zou het vervelend zijn om een ​​aantal bestanden te hebben om dat zo te houden. Ja, we kunnen ons eigen script schrijven waarin we een lijst met bestanden in een array bewaren en dit herhalen, maar we kunnen het beter doen - veel beter. 

Een ander ongewenst neveneffect zou zijn dat telkens wanneer we een dergelijk script uitvoeren, alle HTML-bestanden opnieuw worden opgebouwd, zelfs als ze helemaal niet zijn gewijzigd. Een grote lijst met bestanden zou u veel langer laten wachten of veel meer middelen opnemen dan nodig is. We hebben geen extra code nodig om voor meerdere bestanden te zorgen. Rake doet een betere, efficiëntere taak op die afdeling omdat het alleen zijn bestandstaken of regels uitvoert wanneer het bestand ondertussen is aangeraakt.

Wat Rakefile

regel ".html" => ".markdown" do | rule | sh "pandoc -s # rule.source -o # rule.name" einde

Wanneer we een regel als de bovenstaande definiëren, hebben we een mechanisme geïmplementeerd voor het transformeren van elk bestand met een .markdown uitbreiding in een .html het dossier. Met regels zoekt Rake eerst naar een taak voor een specifiek bestand zoals quartermaster_gadgets.html. Maar als het er geen kan vinden, gebruikt het de vlakte .html regel om naar een bron te zoeken die een succesvolle uitvoering kan bereiken. Op die manier hoeft u geen lange lijst met bestanden te maken, maar gebruikt u alleen een algemene "regel" die bepaalt hoe bepaalde bestandstaken moeten worden afgehandeld. Best gaaf!

Taakobject

In de bovenstaande regel maakten we gebruik van het taakobject, in dit geval een regelobject, om nog preciezer te zijn. We kunnen het als een blokkeringsargument doorgeven aan de sluitings- en oproepmethoden. Net als bij bestandstaken, hebben regels alles te maken met taakbronnen, afhankelijkheden (bijvoorbeeld een markdown-bestand) en de naam van de taak. 

Vanuit de body van regels in het blok (en bestandstaken) hebben we toegang tot de naam en bron van de regels. We kunnen informatie uit dat argument halen, de naam doorgegeven rule.name en de bron (alias bestandsbron) via rule.source. Hierboven kunnen we voorkomen dat de namen van de bestanden worden gekopieerd en in plaats daarvan een patroon worden gegeneraliseerd. Op dezelfde manier kunnen we de lijst met vereisten of afhankelijkheden krijgen rules.prerequisites. Voor bestandstaken of een andere taak geldt hetzelfde natuurlijk.

Over afhankelijkheden gesproken, ze kunnen functioneren als een lijst die moet worden herhaald. Het is niet nodig om een ​​afzonderlijke map te maken elk loop als je je kaarten goed speelt. 

taak: html =>% W [quartermaster_gadgets.html, research_gadgets.html] rule ".html" => ".md" do | r | sh "pandoc -s # r.source -o # r.name" einde

Zoals u ziet, hoefden we de lijst met artikelen niet handmatig opnieuw te doorlopen. We hebben Rake gewoon aan het werk gezet en de afhankelijkheden gebruikt - wat een stuk eenvoudiger en schoner is.

Wat nog cooler is voor DRYing-spullen, is dat regels een proc-object - een anonieme functieobject, in wezen een lambda-als een vereiste kunnen nemen. Dat betekent dat we in plaats van slechts één patroon als voorwaarde een iets dynamischer kunnen doorstaan, waardoor we een net van patronen kunnen maken dat meer dan één enkele vis vangt. Bijvoorbeeld regels voor .markdown en .md bestanden. 

Ze zouden hetzelfde lichaam van de regel hebben, maar alleen een ander patroon als vereiste. Het is als het definiëren van een nieuwe bestandstaak voor elk object dat door het proc-object wordt geretourneerd. Een andere manier om met regels te werken is natuurlijk, reguliere expressies. Je passeert een patroon als een afhankelijkheid en als je een overeenkomst hebt, kan de bestandstaak worden uitgevoerd. Zoete opties, nee?

some_markdown_list = [...] detect_source = proc do | html_file_name | some_markdown_list.detect | markdown_source | markdown_source.ext == html_file_name.ext eindregel '.html' => detect_source do | r | sh "pandoc -s # r.source -o # r.name" einde

Het verschil tussen Procs en Lambdas

Als je nog geen ervaring hebt met lambda-land of het nog niet helemaal uitgevonden hebt, is hier een kleine opfriscursus. Procs zijn objecten die je kunt doorgeven en die later kunnen worden uitgevoerd, net als lambda's. Beide zijn overigens Proc-objecten. Het verschil is subtiel en komt neer op de argumenten die erin worden doorgegeven. Lambda's controleren het aantal argumenten en kunnen opblazen met een ArgumentError om die reden maakt het mij niet uit. Het andere verschil is met betrekking tot de verwerking van retourneringen. Procs gaat uit van de scope waar het proc-object werd uitgevoerd. Lambda's verlaten gewoon de lambda-scope en blijven de volgende code activeren die in lijn is, om zo te zeggen. Niet super belangrijk hier, maar ik dacht voor de nieuwkomers onder jullie, het kan ook geen kwaad doen.

Handige vlaggen

Dit is een korte lijst met vlaggen die u kunt doorgeven om taken te harken.

  • --reglement

Laat zien hoe Rake regels probeert toe te passen, een spoor voor regels. Van onschatbare waarde als je een paar regels hanteert en tegen bugs loopt.

schelp

$ rake quartermaster_gadgets.html --regels Poging tot regel quartermaster_gadgets.html => quartermaster_gadgets.md (quartermaster_gadgets.html => quartermaster_gadgets.md ... EXIST) pandoc -s quartermaster_gadgets.md -o quartermaster_gadgets.html
  • -t

Herinner de solve_bonnie_situation taak van artikel één? Laten we deze vlag toevoegen aan deze Rake-taak en tracering inschakelen. We krijgen ook een backtrace als we fouten tegenkomen. Dit is zeker handig voor foutopsporing.

schelp

$ rake solve_bonnie_situation -t ** Invraag solve_bonnie_situation (first_time) ** Invest get_mr_wolf (first_time) ** Execute get_mr_wolf Je hebt geen probleem Jules, ik ben er mee bezig! Ga daar binnen en ontspan ze en wacht op de wolf die direct zou moeten komen! ** Roep kalm_down_jimmy op (first_time) ** Voer kalm_down_jimmy uit Jimmy, doe me een lol, wil je? Ik rook daar wat koffie. Zou je een kopje voor me maken? ** Invoke figure_out_bonnie_situation (first_time) ** Execute figure_out_bonnie_situation Als ik correct werd geïnformeerd, tikt de klok. Klopt dat Jimmy? ** Invoke get_vince_vega_in_line (first_time) ** Execute get_vince_vega_in_line Kom opnieuw? Krijg het rechte buster. Ik ben hier niet om te zeggen alsjeblieft! Ik ben hier om je te vertellen wat je moet doen! ** Roep clean_car aan (first_time) ** Execute clean_car Ik heb twee jongens nodig om die schoonmaakproducten te nemen en de binnenkant van de auto schoon te maken. Ik praat snel, snel, snel! ** Invoke clean_crew (first_time) ** Execute clean_crew Jim, the soap! OK. Heren, jullie zijn allebei naar de provincie geweest voor ik het zeker weet. Hier komt het! ** Invoke get_rid_of_evidence_at_monster_joes (first_time) ** Execute get_rid_of_evidence_at_monster_joes Dus wat is er met de outfits? Gaan jullie naar een volleybalwedstrijd of zo? ** Roep drive_into_the_sunrise (first_time) aan ** Voer drive_into_the_sunrise uit Bel me Winston! ** Voer solve_bonnie_situation uit. Weet je, ik zou gaan ontbijten. Ik heb zin om met mij te ontbijten?

Traceerregels

omgeving Rake.application.options.trace_rules = true in een Rakefile vertelt Rake ons zelf om ons informatie over regels te laten zien wanneer we een taak uitvoeren. Dit is gaaf, want als we een trace uitvoeren via hark -t, met één vlag ontvangen we alle benodigde foutopsporingsinformatie. We krijgen niet alleen een lijst met taakaanroepen, maar kunnen ook zien welke regels zijn toegepast of geprobeerd.

  • -P

Toont een lijst met vereisten voor alle taken. Hier gebruiken we opnieuw de solve_bonnie_situation taak. Het weglaten van de uitvoer voor andere taken, dit zou zijn eenmalige output zijn:

schelp

$ rake solve_bonnie_situation -P ... rake solve_bonnie_situation get_mr_wolf calm_down_jimmy figure_out_bonnie_situation get_vince_vega_in_line clean_car clean_crew get_rid_of_evidence_at_monster_joes drive_into_the_sunrise ... 

Als je nieuwsgierig bent, ren dan hark -P. Vrij interessante output.

  • -m

Voert taken uit als multitasks.

Parallelle taken

Laat me je voorstellen aan de multitasken methode. Dit kan u helpen om de zaken een beetje te versnellen - we hebben immers meerdere cores op de meeste moderne computers, dus laten we er gebruik van maken. Natuurlijk kun je de snelheidsboost altijd bereiken door solide code te schrijven die geen vet bevat, maar het tegelijkertijd uitvoeren van taken kan je in dat opzicht zeker iets extra's geven. Er zijn echter valkuilen die we ook zullen behandelen.

De taken die we tot nu toe hebben uitgevoerd, voeren achtereenvolgens alle taken achter elkaar uit. Het is een veilige gok als je code in orde is, maar het is ook langzamer. Als snelheid belangrijk is voor een bepaalde taak, kunnen we een beetje helpen door multi-threading taken. Houd er echter rekening mee dat de sequentiële benadering onder bepaalde omstandigheden de betere optie is.

Laten we zeggen dat we drie Rake-taken hebben die moeten worden uitgevoerd als een vereiste om een ​​vierde taak uit te voeren. Dit zijn in principe vier threads. In het grotere plaatje van dingen, wanneer je meerdere applicaties uitvoert - of om specifieker te zijn, processen - is hetzelfde idee aan het werk.

multitask: shoot_bond_movie => [: shoot_car_chase,: shoot_love_scene,: shoot_final_confrontation] do puts "De belangrijkste fotografie is voltooid en we kunnen beginnen met bewerken." einde

Gebruik makend van multitasken, de afhankelijkheden in onze prerequisite-array worden nu niet meer in deze volgorde uitgevoerd. In plaats daarvan worden ze verspreid en parallel uitgevoerd, maar vóór de shoot_bond_movie taak, natuurlijk. Een Ruby-thread voor elke taak wordt op hetzelfde moment uitgevoerd. Als ze klaar zijn, shoot_bond_movie zal zijn zaken doen. De manier waarop taken hier werken is vergelijkbaar met randomisatie, maar in feite worden ze eenvoudigweg tegelijkertijd uitgevoerd.

Het lastige is alleen maar om ervoor te zorgen dat bepaalde afhankelijkheden worden verwerkt in een volgorde die past bij uw behoeften. Daarom moeten we zorgen voor de raceomstandigheden. Dit betekent in feite dat een of andere taak in de problemen komt omdat de volgorde van uitvoering onbedoelde neveneffecten had. Het is een bug. 

Als we dat kunnen voorkomen, bereiken we draadveiligheid. Met betrekking tot gemeenschappelijke voorwaarden, interessant genoeg, zullen deze vereisten slechts één keer worden uitgevoerd, omdat de multitask-vereisten eerst op hun voltooiing wachten.

taak: shoot_love_scene do ... taak beëindigen: prepare_italy_set do ... taak beëindigen: shoot_car_chase => [: prepare_italy_set] do ... taak beëindigen: shoot_final_confrontation => [: prepare_italy_set] do ... end multitask: shoot_bond_movie => [: shoot_car_chase,: shoot_love_scene,: shoot_final_confrontation ] do puts "De belangrijkste fotografie is voltooid en we kunnen beginnen met bewerken." einde

Beide shoot_car_chase en shoot_final_confrontation taken zijn afhankelijk van prepare_italy_set om als eerste te eindigen - die overigens maar één keer wordt uitgevoerd. We kunnen dat mechanisme gebruiken om de volgorde te voorspellen wanneer taken parallel worden uitgevoerd. Vertrouw niet alleen de volgorde van uitvoering als het op een of andere manier belangrijk is voor uw taak.

Laatste gedachten

Nou, ik denk dat je nu volledig uitgerust bent om een ​​aantal serieuze Rake-zaken te schrijven. Het juiste gebruik van deze tool zal je leven als een Ruby-ontwikkelaar hopelijk nog blijer maken. In dit tweede artikel hoop ik dat ik kon overbrengen wat een eenvoudig maar geweldig hulpmiddel is dat Rake echt is. Het is gemaakt door een echte meester van zijn vak.  

We zijn allemaal enorm veel respect verschuldigd aan Jim Weirich voor het bedenken van deze elegante bouwtool. De Ruby-gemeenschap is zeker niet helemaal hetzelfde sinds hij stierf. De erfenis van Jim is duidelijk hier om te blijven. Een andere reus waar we het voorrecht op hebben om verder te bouwen.