File Coverage

blib/lib/App/InteractivePerlTutorial.pm
Criterion Covered Total %
statement 11 11 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 15 15 100.0


line stmt bran cond sub pod time code
1             package App::InteractivePerlTutorial;
2              
3 1     1   21391 use 5.014000;
  1         3  
4 1     1   5 use strict;
  1         2  
  1         22  
5 1     1   4 use warnings;
  1         6  
  1         38  
6 1     1   755 use parent qw/Wx::App/;
  1         296  
  1         6  
7             our $VERSION = '0.000_001';
8              
9             use constant CHAPTERS => [qw/Introduction Scalar Arrays Hashes Sub IO FileTests Control Smartmatch/];
10              
11             use File::HomeDir;
12             use Pod::Simple::XHTML;
13             use YAML::Any qw/DumpFile LoadFile/;
14              
15             use Wx qw/:everything/;
16             use Wx::Event qw/:everything/;
17             use Wx::Html;
18             use Wx::Scintilla;
19              
20             ##################################################
21              
22             my ($tree, $frame, $sizer, $html_window, $exe_panel, $exe_nb, $scintilla, $lesson, @exercises);
23             my ($datafile, %exdata);
24             my $purple = Wx::Colour->new(65, 67,109);
25              
26             sub add_subtree{
27             my ($tree, $node, $parent) = @_;
28             eval "require $node" or die $@;
29             my $item = $tree->AppendItem($parent, $node->TEXT);
30             $tree->SetItemBackgroundColour($item, $purple);
31             $tree->SetItemTextColour($item, $node->can('COLOUR') ? Wx::Colour->new($node->COLOUR) : Wx::Colour->new('white'));
32             if ($node->can('EXERCISES')) {
33             my @exercises = @{$node->EXERCISES};
34             $tree->SetItemImage($item, 0) if @exercises == grep { $exdata{$node}[$_] } 0 .. $#exercises;
35             } elsif ($node->can('CHILDREN')) {
36             $tree->SetItemImage($item, 2);
37             $tree->SetItemTextColour($item, Wx::Colour->new('lightgreen'));
38             $tree->SetItemBold($item);
39             }
40             $tree->SetPlData($item, $node);
41             eval { add_subtree ($tree, "${node}::$_", $item) for @{$node->CHILDREN()} };
42             }
43              
44             sub run_exercise{
45             my $code = $scintilla->GetText;
46             my $exercise = $exercises[$exe_nb->GetSelection];
47             undef $@;
48             my ($in, $out) = $exercise->{input};
49             close STDIN;
50             close STDOUT;
51             open STDIN, '<', \$in or die "Cannot reopen STDIN: $!";
52             open STDOUT, '>', \$out or die "Cannot reopen STDOUT: $!";
53             my @ret = eval $code;
54             my $dialog;
55             if ($@) {
56             $dialog = Wx::MessageDialog->new($frame, "An error has ocurred while executing the code\n\n$@", 'Error', wxOK | wxCENTRE | wxICON_ERROR);
57             } elsif ($exercise->{check}->($out, @ret)){
58             $dialog = Wx::MessageDialog->new($frame, 'You solved this exercise successfully', 'Success', wxOK | wxCENTRE);
59             $exdata{$lesson}[$exe_nb->GetSelection] = 1;
60             #$exdata{$lesson}{alldone} = 1 if ;
61             DumpFile $datafile, \%exdata;
62             $tree->DeleteAllItems;
63             my $root = $tree->AddRoot('root');
64             add_subtree $tree, "App::InteractivePerlTutorial::Chapter::$_", $root for @{CHAPTERS()};
65             $tree->ExpandAll;
66             } else {
67             $dialog = Wx::MessageDialog->new($frame, 'Wrong answer', 'Wrong answer', wxOK | wxCENTRE);
68             }
69             $dialog->ShowModal;
70             }
71              
72             sub show_hint{
73             Wx::MessageDialog->new($frame, $exercises[$exe_nb->GetSelection]->{hint} // 'No hint available', 'Success', wxOK | wxCENTRE)->ShowModal;
74             }
75              
76             sub show_solution{
77             Wx::MessageDialog->new($frame, $exercises[$exe_nb->GetSelection]->{solution} // 'No solution available', 'Success', wxOK | wxCENTRE)->ShowModal;
78             }
79              
80             sub show_questions {
81             Wx::TextEntryDialog->new($frame, "Do you have any questions about Interactive Perl Tutorial?\nWrite your question in the box below and press OK.", 'Questions?', wxOK | wxCANCEL)->ShowModal;
82             }
83              
84             use Data::Dumper;
85              
86             sub mangle_lesson {
87             my $lesson = '' . shift;
88             $lesson =~ s!()!\U$1\E!;
89             $lesson =~ s,,,g;
90             $lesson =~ s,,,g;
91             $lesson
92             }
93              
94             sub change_lesson {
95             my $module = shift;
96             $lesson = $module;
97              
98             my $parser = Pod::Simple::XHTML->new;
99             my $out;
100             $parser->output_string (\$out);
101             my $fh;
102             { no strict 'refs'; $fh = *{"${module}::DATA"} }
103             my $pos = tell $fh;
104             $parser->parse_file($fh);
105             seek $fh, $pos, 0;
106             $html_window->SetPage(mangle_lesson $out);
107              
108             if ($module->can('EXERCISES')) {
109             $exe_panel->Show(1);
110             $exe_nb->DeleteAllPages;
111             @exercises = @{$module->EXERCISES};
112             for (0 .. $#exercises) {
113             $exe_nb->AddPage(Wx::StaticText->new($exe_nb, -1, $exercises[$_]{statement}), 'Exercise ' . ($_ + 1));
114             }
115             } else {
116             $exe_panel->Show(0);
117             }
118              
119             $sizer->Layout;
120             }
121              
122             sub init_scintilla{
123             my $font = Wx::Font->new(12, wxTELETYPE, wxNORMAL, wxNORMAL);
124             $scintilla->SetFont($font);
125             $scintilla->StyleSetFont(Wx::Scintilla::STYLE_DEFAULT, $font);
126             $scintilla->StyleClearAll();
127             $scintilla->StyleSetBackground(Wx::Scintilla::STYLE_DEFAULT, $purple);
128             $scintilla->StyleSetBackground($_, $purple) for 0 .. 18;
129             $scintilla->StyleSetForeground(0, Wx::Colour->new(255, 255, 127));
130             $scintilla->StyleSetForeground(1, Wx::Colour->new(0, 255, 255));
131             $scintilla->StyleSetForeground(2, Wx::Colour->new(255, 127, 255));
132             $scintilla->StyleSetForeground(3, Wx::Colour->new(127, 127, 127));
133             $scintilla->StyleSetForeground(4, Wx::Colour->new(255, 127, 127));
134             $scintilla->StyleSetForeground(5, Wx::Colour->new(255, 255, 127));
135             $scintilla->StyleSetForeground(6, Wx::Colour->new(0, 127, 255));
136             $scintilla->StyleSetForeground(7, Wx::Colour->new(127, 255, 127));
137             $scintilla->StyleSetForeground(8, Wx::Colour->new(255, 255, 255));
138             $scintilla->StyleSetForeground(9, Wx::Colour->new(127, 127, 127));
139             $scintilla->StyleSetForeground(10, Wx::Colour->new(255, 255, 127));
140             $scintilla->StyleSetForeground(11, Wx::Colour->new(255, 255, 0));
141             $scintilla->StyleSetForeground(12, Wx::Colour->new(127, 255, 127));
142             $scintilla->StyleSetForeground(13, Wx::Colour->new(191, 128, 0));
143             $scintilla->StyleSetForeground(17, Wx::Colour->new(0, 255, 127));
144             $scintilla->StyleSetForeground(18, Wx::Colour->new(127, 127, 255));
145              
146             $scintilla->StyleSetBold(12, 1);
147             $scintilla->StyleSetSpec(Wx::Scintilla::SCE_H_TAG, 'fore:#0000ff');
148             $scintilla->SetLexer(Wx::Scintilla::SCLEX_PERL);
149             }
150              
151             my ($onion, $tick_cross);
152              
153             sub load_png {
154             my $handler = Wx::PNGHandler->new;
155             my $image = Wx::Image->new;
156             open my $file, shift;
157             $handler->LoadFile($image, $file);
158             Wx::Bitmap->new($image);
159             }
160              
161             sub load_images {
162             $onion = load_png 'onion.png';
163             $tick_cross = Wx::ImageList->new(20, 20);
164             $tick_cross->Add(load_png 'tick.png');
165             $tick_cross->Add(load_png 'cross.png');
166             $tick_cross->Add(load_png 'minionion.png');
167             }
168              
169             sub OnInit {
170             load_images;
171             $frame = Wx::Frame->new(undef, -1, 'Interactive Perl Tutorial', [-1, -1], [500, 700]);
172             my $panel = Wx::Panel->new($frame);
173             $panel->SetBackgroundColour(Wx::Colour->new(59, 12, 89));
174             $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
175              
176             $tree = Wx::TreeCtrl->new($panel, -1, [-1, -1], [-1, -1], wxTR_DEFAULT_STYLE | wxTR_HIDE_ROOT);
177             $tree->SetBackgroundColour($purple);
178             $tree->SetImageList($tick_cross);
179             my $root = $tree->AddRoot('root');
180             add_subtree $tree, "App::InteractivePerlTutorial::Chapter::$_", $root for @{CHAPTERS()};
181             EVT_TREE_SEL_CHANGED($tree, $tree, sub { change_lesson $_[0]->GetPlData($_[0]->GetSelection) });
182             $tree->ExpandAll;
183              
184             $html_window = Wx::HtmlWindow->new($panel);
185              
186             $exe_panel = Wx::Panel->new($panel);
187             $exe_panel->SetBackgroundColour(Wx::Colour->new(59, 12, 89));
188             my $exe_sizer = Wx::BoxSizer->new(wxVERTICAL);
189             $exe_nb = Wx::Notebook->new($exe_panel);
190             $exe_nb->SetBackgroundColour($purple);
191             $exe_nb->SetForegroundColour(Wx::Colour->new('white'));
192             $scintilla = Wx::Scintilla::TextCtrl->new($exe_panel);
193             init_scintilla $scintilla;
194             $scintilla->SetWhitespaceBackground(1, $purple);
195             my $button_panel = Wx::Panel->new($exe_panel);
196             $button_panel->SetBackgroundColour(Wx::Colour->new(59, 12, 89));
197             my $button_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
198             my $run = Wx::BitmapButton->new($button_panel, -1, $onion);
199             my $hint = Wx::Button->new($button_panel, -1, 'Hint');
200             my $sol = Wx::Button->new($button_panel, -1, 'Solution');
201             my $questions = Wx::Button->new($button_panel, -1, 'Questions?');
202             EVT_BUTTON($run, $run, \&run_exercise);
203             EVT_BUTTON($hint, $hint, \&show_hint);
204             EVT_BUTTON($sol, $sol, \&show_solution);
205             EVT_BUTTON($questions, $questions, \&show_questions);
206              
207             $button_sizer->Add($run, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, 5);
208             $button_sizer->Add($hint, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, 5);
209             $button_sizer->Add($sol, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, 5);
210             $button_sizer->Add($questions, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, 5);
211             $button_panel->SetSizer($button_sizer);
212              
213             $exe_sizer->Add($exe_nb, 1, wxEXPAND | wxBOTTOM, 10);
214             $exe_sizer->Add($scintilla, 3, wxEXPAND | wxTOP | wxBOTTOM, 10);
215             $exe_sizer->Add($button_panel, 0, wxCENTER | wxTOP, 10);
216             $exe_panel->SetSizer($exe_sizer);
217              
218             change_lesson "App::InteractivePerlTutorial::Chapter::Introduction";
219             $sizer->Add($tree, 1, wxEXPAND | wxALL, 10);
220             $sizer->Add($html_window, 3, wxEXPAND | wxALL, 10);
221             $sizer->Add($exe_panel, 2, wxEXPAND | wxALL, 10);
222             $panel->SetSizer($sizer);
223             $frame->Show(1)
224             }
225              
226             sub run{
227             $datafile = File::HomeDir->my_dist_data(__PACKAGE__, {create => 1}) . '/exercises.yml';
228             %exdata = %{LoadFile $datafile} if -f $datafile;
229             App::InteractivePerlTutorial->new->MainLoop;
230             }
231              
232             1;
233             __END__