1 ///
2 module dli.index_menu;
3 
4 import dli.text_menu;
5 import dli.exceptions;
6 import std.conv;
7 import std.exception;
8 import std.stdio;
9 import std.typecons : Tuple, tuple;
10 
11 ///
12 public class IndexMenu(inputStreamT, outputStreamT) : TextMenu!(inputStreamT, outputStreamT, size_t)
13 {
14     private size_t highestMenuItemIndex;
15 
16     ///
17     public this(inputStreamT inputStream, outputStreamT outputStream)
18     {
19         super(inputStream, outputStream);
20     }
21 
22     ///
23     public override void addItem(MenuItem item, size_t key)
24     {
25         super.addItem(item, key);
26 
27         import std.algorithm.comparison : max;
28 
29         highestMenuItemIndex = max(highestMenuItemIndex, key);
30     }
31 
32     /// Adds the given item, automatically assigning the next highest available key
33     public void addItem(MenuItem item)
34     {
35         addItem(item, highestMenuItemIndex + 1);
36     }
37 
38     protected override void addExitMenuItem(MenuItem exitMenuItem)
39     {
40         menuItems[highestMenuItemIndex + 1] = exitMenuItem;
41     }
42 
43     protected override void removeExitMenuItem()
44     {
45         menuItems.remove(highestMenuItemIndex + 1);
46     }
47 
48     protected override Tuple!(size_t, MenuItem)[] sortItemsForDisplay()
49     {
50         // It is important that the items are printed in the correct order
51         import std.algorithm.sorting : sort;
52         auto sortedKeys = sort(menuItems.keys);
53 
54         Tuple!(size_t, MenuItem)[] sortedItems;
55 
56         foreach(size_t menuItemIndex; sortedKeys)
57             sortedItems ~= tuple(menuItemIndex, menuItems[menuItemIndex]);
58 
59         return sortedItems;        
60     }
61 }
62 
63 ///
64 public auto createIndexMenu(File inStream = stdin, File outStream = stdout)
65 {
66     return new IndexMenu!(File, File)(inStream, outStream);
67 }
68 
69 ///
70 public auto createIndexMenu(inputStreamT, outputStreamT)(inputStreamT inStream, outputStreamT outStream)
71 {
72     return new IndexMenu!(inputStreamT, outputStreamT)(inStream, outStream);
73 }
74 
75 // TESTS
76 version(unittest)
77 {
78     import dli.string_stream.input_string_stream;
79     import dli.string_stream.output_string_stream;
80     import test.dli.mock_menu_item;
81 
82     @("Test IndexMenu.addItem(MenuItem) properly places items")
83     unittest
84     {
85         auto inputStream = new shared InputStringStream();
86         auto menu = createIndexMenu(inputStream, new shared OutputStringStream());
87         auto item1 = new MockMenuItem();
88         auto item2 = new MockMenuItem();
89         // We deliberately skip item 3
90         auto item4 = new MockMenuItem();
91         auto item5 = new MockMenuItem();
92 
93         menu.addItem(item1);
94         menu.addItem(item2);
95         menu.addItem(item4, 4); // This one we specify the key
96         menu.addItem(item5);
97 
98         inputStream.appendLine("1");
99         inputStream.appendLine("2");
100         inputStream.appendLine("6"); // 6 should correspond to the exit menu item
101         menu.run();
102 
103         assert(item1.executed);
104         assert(item2.executed);
105         assert(!item4.executed);
106         assert(!item5.executed);
107 
108         inputStream.appendLine("4");
109         inputStream.appendLine("5");
110         inputStream.appendLine("6");
111         menu.run();
112 
113         assert(item4.executed);
114         assert(item5.executed);
115     }
116 }