Skip to content

Commit 09c6273

Browse files
committed
Added func import and func export capability
Updated code, help, and user guide jUnit dependencies were updated
1 parent 126f133 commit 09c6273

File tree

5 files changed

+160
-34
lines changed

5 files changed

+160
-34
lines changed
Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<img align="right" width="150" src="../Images/UDF.png">
1+
<img align="right" width="150" src="../Images/UDF.png" alt="">
22

33
# User Defined Functions
44

@@ -8,13 +8,15 @@ When recording a new function, start by adding data to your stack that would emu
88

99
When recording is complete, enter `record off` to complete your recording. You'll be prompted for a function name. Your new command will be this name so choose a name without spaces and that is easy to type when you wish to execute the function in the future. You should also not choose a name of an existing command as your function will not be able to be called. If you do not provide a name, i.e, just hit enter, the recording will be discarded.
1010

11-
Then you can run your function whever you like on the stack currently available. To run the function, simply type the name of your function as a standard command. To see a list of the saved functions, execute `list func` and it will display the name and the steps you recorded.
11+
Then you can run your function whenever you like on the stack currently available. To run the function, simply type the name of your function as a standard command. To
12+
see a list of the saved functions, execute `list func` and it will display the name and the steps you recorded.
1213

1314
User defined functions can be deleted with the `func del NAME` command or you can delete all of the functions with `func delall`
1415

15-
Functions are global and can work across any stack. They are saved in the preferences system and will be reloaded when RPNCalc starts. They are saved immediatly after you give a new recording a name and press enter.
16+
Functions are global and can work across any stack. They are saved in the preferences system and will be reloaded when RPNCalc starts. They are saved immediately after you give a new recording a name and press enter.
1617

17-
When you execute a function, the steps of that function are executed one after the other. Therefore when you execute `undo` you will undo back through your function step by step. You do not `undo` the entire function in one command. Of course you can always run `undo NUM` where `NUM` is the steps to undo.
18+
When you execute a function, the steps of that function are executed one after the other. Therefore when you execute `undo` you will undo back through your function step
19+
by step. You do not `undo` the entire function in one command. Of course, you can always run `undo NUM` where `NUM` is the steps to undo.
1820

1921
The following commands can be entered during a recording, but are not recorded.
2022
- list
@@ -26,14 +28,17 @@ The following commands can be entered during a recording, but are not recorded.
2628
- set, reset
2729
- cx, x, quit, exit
2830

29-
|<div style="width:110px">Command</div>|Description|
30-
|-------|-----------|
31-
|record on<br>rec on|Turn on recording. Most commands and numbers entered after record is enabled will be saved. There are some that are excluded from being recorded as detailed above|
32-
|record off<br>rec off| Turn off recording. The user will be prompted to enter in a name for this function and that name will be used to run it in the future. If you do not enter in a name the recording is canceled and nothing will be saved|
33-
|func del `NAME`|Delete a saved function. The name must match the one given when saved. A list of functions can be viewed with `list func`. Undo will not recover a deleted function|
34-
|func delall|Delete all saved user defined functions. Please note that undo will not recover deleted functions|
31+
|<div style="width:110px">Command</div>| Description |
32+
|-------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
33+
|record on<br>rec on| Turn on recording. Most commands and numbers entered after record is enabled will be saved. There are some that are excluded from being recorded as detailed above |
34+
|record off<br>rec off| Turn off recording. The user will be prompted to enter in a name for this function and that name will be used to run it in the future. If you do not enter in a name the recording is canceled and nothing will be saved |
35+
|func del `NAME`| Delete a saved function. The name must match the one given when saved. A list of functions can be viewed with `list func`. Undo will not recover a deleted function |
36+
|func delall| Delete all saved user defined functions. Please note that undo will not recover deleted functions |
37+
|func export| Export all User Defined Functions to a file for backup or future import |
38+
|func import| Import a previously exported set of functions. Please note that importing will append the imported functions to the currently defined list. If an imported function has the same name as an existing one, the existing function will be overwritten |
3539

36-
### Example
40+
41+
### Example 1
3742
Here is a full real world example. Lets say you'd like to make a user defined function to take the cube of number. Here are the steps you'd take to do that.
3843

3944
|Number|Command|Description|
@@ -46,4 +51,34 @@ Here is a full real world example. Lets say you'd like to make a user defined f
4651
|6|`record off`|Stop recording and give the function the name "cube"|
4752
|7|`list func`|Show the user defined functions including the `cube` command you just made|
4853

49-
From now on, just type `cube` and `line1` will be cubed! Go You!
54+
From now on, just type `cube` and `line1` will be cubed! Go You! You can always list your defined functions with `list func`
55+
56+
### Example 2
57+
Another real world but slightly more complex example. Say you wish to know the percentage different between two numbers. Let's create a function called `diffpercent` The
58+
logic is to find the smallest number on the stack and then the largest. Divide them. Flip the sign and add one to it. Then convert it to a percent.
59+
60+
| Number | Command | Description |
61+
|:------:|:---------------:|:------------------------------------------------------------------------------------|
62+
| 1 | `c` | Clear the stack. Not really needed, but let's start off tidy |
63+
| 2 | `10` | Setting up two numbers before we record the function |
64+
| 3 | `1` | Setting up two numbers before we record the function |
65+
| 4 | `record on` | Start recording. From now on anything you do (mostly) will be recorded |
66+
| 5 | `min` | Copy the smallest number in the stack to the top |
67+
| 6 | `max` | Copy the largest number in the stack to the top |
68+
| 7 | `/` | Divide them |
69+
| 8 | `f` | Flip the sign |
70+
| 9 | `1` | Add the number one to the stack |
71+
| 10 | `+` | Add the numbers together (effectively subtracting the division results from 1 |
72+
| 11 | `to%` | Convert to a percent (multiply by 100) |
73+
| 12 | `record off` | Save the function with the name `diffpercent` |
74+
75+
In the example above, the result it 90%. There is a 90% different between 10 and 1. Going forward, you can use the command `diffpercent` anytime you need to find the
76+
percentage different between the lowest and highest number on the stack.
77+
78+
79+
80+
81+
82+
83+
84+

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
<groupId>org.fross</groupId>
55
<artifactId>rpncalc</artifactId>
6-
<version>5.6.1</version>
6+
<version>5.7.0</version>
77
<packaging>jar</packaging>
88

99
<name>rpncalc</name>
@@ -279,7 +279,7 @@
279279
<dependency>
280280
<groupId>org.junit.jupiter</groupId>
281281
<artifactId>junit-jupiter</artifactId>
282-
<version>5.13.0-M2</version>
282+
<version>5.13.0-M3</version>
283283
<scope>test</scope>
284284
</dependency>
285285

snap/snapcraft.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: rpncalc
2-
version: '5.6.1'
2+
version: '5.7.0'
33
summary: The command line Reverse Polish Notation (RPN) calculator
44
description: |
55
RPNCalc is an easy to use command line based Reverse Polish

src/main/java/org/fross/rpncalc/Help.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ public static void Display() {
134134
Output.printColorln(Ansi.Color.YELLOW, "\nUser Defined Functions:");
135135
Output.printColorln(Ansi.Color.WHITE, " func del NAME Delete named user defined function");
136136
Output.printColorln(Ansi.Color.WHITE, " func delall Delete all user defined functions");
137+
Output.printColorln(Ansi.Color.WHITE, " func export Export the currently defined functions to a file");
138+
Output.printColorln(Ansi.Color.WHITE, " func import Import functions from a file and append them to current ones");
137139
Output.printColorln(Ansi.Color.WHITE, " record on Turn on command recording");
138140
Output.printColorln(Ansi.Color.WHITE, " record off Disable recording");
139141

src/main/java/org/fross/rpncalc/UserFunctions.java

Lines changed: 108 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
import org.fusesource.jansi.Ansi;
3131
import org.jline.reader.UserInterruptException;
3232

33+
import java.io.File;
34+
import java.io.FileInputStream;
35+
import java.io.FileOutputStream;
36+
import java.io.IOException;
3337
import java.util.ArrayList;
3438
import java.util.prefs.BackingStoreException;
3539
import java.util.prefs.Preferences;
@@ -133,31 +137,116 @@ public static void cmdRecord(String args) {
133137
*/
134138
public static void cmdFunction(String args) {
135139
String[] command = args.toLowerCase().trim().split("\\s", 2);
140+
String fileNameStr = "";
136141

137142
try {
138-
if (command[0].equals("del")) {
139-
try {
140-
FunctionDelete(command[1]);
141-
} catch (ArrayIndexOutOfBoundsException ex) {
142-
Output.printColorln(Ansi.Color.RED, "ERROR: 'function del' requires a valid function name to delete");
143-
}
143+
switch (command[0]) {
144+
case "del":
145+
try {
146+
FunctionDelete(command[1]);
147+
} catch (ArrayIndexOutOfBoundsException ex) {
148+
Output.printColorln(Ansi.Color.RED, "ERROR: 'function del' requires a valid function name to delete");
149+
}
150+
break;
144151

145-
} else if (command[0].equals("delall")) {
146-
Preferences p = Preferences.userRoot().node(UserFunctions.PREFS_PATH_FUNCTIONS);
152+
case "delall":
153+
Preferences p = Preferences.userRoot().node(UserFunctions.PREFS_PATH_FUNCTIONS);
147154

148-
// Loop through each function (child of the root) and delete it
149-
try {
150-
for (String functionName : p.childrenNames()) {
151-
Output.debugPrintln("Removing function: " + functionName);
152-
FunctionDelete(functionName);
155+
// Loop through each function (child of the root) and delete it
156+
try {
157+
for (String functionName : p.childrenNames()) {
158+
Output.debugPrintln("Removing function: " + functionName);
159+
FunctionDelete(functionName);
160+
}
161+
} catch (BackingStoreException e) {
162+
Output.printColorln(Ansi.Color.RED, "Error: Could not remove the user defined functions");
153163
}
154-
} catch (BackingStoreException e) {
155-
Output.printColorln(Ansi.Color.RED, "Error: Could not remove the user defined functions");
156-
}
164+
break;
157165

158-
} else {
159-
Output.printColorln(Ansi.Color.RED, "ERROR: Illegal argument for function command. Please see help");
166+
case "export":
167+
Output.printColorln(Ansi.Color.YELLOW, "Please enter the export filename. A blank name will cancel the export");
168+
Output.printColorln(Ansi.Color.YELLOW, "Note, if needed, please use use '/' as the path separator");
169+
170+
// Read the user input. If ctrl-C is entered, discard the function
171+
try {
172+
fileNameStr = Main.scanner.readLine(Main.INPUT_PROMPT);
173+
174+
} catch (UserInterruptException ex) {
175+
fileNameStr = "";
176+
} catch (Exception e) {
177+
Output.fatalError("Could not read user input", 5);
178+
}
179+
180+
// If no name is given, cancel export
181+
if (fileNameStr.isBlank()) {
182+
Output.printColorln(Ansi.Color.YELLOW, "Canceling export");
183+
break;
184+
}
185+
186+
// Output the full path/name of the output file
187+
Output.printColorln(Ansi.Color.CYAN, "Export filename: '" + new File(fileNameStr).getAbsolutePath() + "'");
188+
189+
// Output preferences as an XML file
190+
try {
191+
FileOutputStream fos = new FileOutputStream(fileNameStr);
192+
Preferences pExport = Preferences.userRoot().node(PREFS_PATH_FUNCTIONS);
193+
pExport.exportSubtree(fos);
194+
fos.close();
195+
196+
} catch (IOException ex) {
197+
Output.printColorln(Ansi.Color.RED, "Error: RPNCalc can't write to '" + fileNameStr + "'");
198+
} catch (BackingStoreException ex) {
199+
Output.printColorln(Ansi.Color.RED, "Error: Could not export the user defined functions (BackingStoreException)");
200+
} catch (IllegalStateException ex) {
201+
Output.printColorln(Ansi.Color.RED, "Error: Could not export the user defined functions (IllegalStateException)");
202+
} catch (Exception ex) {
203+
Output.printColorln(Ansi.Color.RED, "Error: Could not export the user defined functions (Exception)");
204+
}
205+
break;
206+
207+
case "import":
208+
Output.printColorln(Ansi.Color.YELLOW, "Please enter the import filename. A blank name will cancel the import");
209+
Output.printColorln(Ansi.Color.YELLOW, "Note, if needed, please use use '/' as the path separator");
210+
211+
// Read the user input. If ctrl-C is entered, discard the function
212+
try {
213+
fileNameStr = Main.scanner.readLine(Main.INPUT_PROMPT);
214+
215+
} catch (UserInterruptException ex) {
216+
fileNameStr = "";
217+
} catch (Exception e) {
218+
Output.fatalError("Could not read user input", 5);
219+
}
220+
221+
// If no name is given, cancel export
222+
if (fileNameStr.isBlank()) {
223+
Output.printColorln(Ansi.Color.YELLOW, "Canceling import");
224+
break;
225+
}
226+
227+
// Import preferences from the XML file
228+
try {
229+
FileInputStream fis = new FileInputStream(fileNameStr);
230+
Preferences pImport = Preferences.userRoot().node(PREFS_PATH_FUNCTIONS);
231+
pImport.importPreferences(fis);
232+
fis.close();
233+
234+
Output.printColorln(Ansi.Color.CYAN, "Import complete");
235+
236+
} catch (IOException ex) {
237+
Output.printColorln(Ansi.Color.RED, "Error: RPNCalc can't read from '" + fileNameStr + "'");
238+
} catch (IllegalStateException ex) {
239+
Output.printColorln(Ansi.Color.RED, "Error: Could not import the user defined functions (IllegalStateException)");
240+
} catch (Exception ex) {
241+
Output.printColorln(Ansi.Color.RED, "Error: Could not import the user defined functions (Exception)");
242+
}
243+
break;
244+
245+
default:
246+
Output.printColorln(Ansi.Color.RED, "ERROR: Illegal argument for function command. Please see help");
247+
break;
160248
}
249+
161250
} catch (StringIndexOutOfBoundsException ex) {
162251
// User did not enter a command
163252
Output.printColorln(Ansi.Color.RED, "ERROR: An argument is requirement for function command. Please see help");
@@ -215,7 +304,7 @@ public static void RemoveItemFromRecording(int index) {
215304
* RemoveItemFromRecording(): If no argument is given, remove the last item
216305
*/
217306
public static void RemoveItemFromRecording() {
218-
RemoveItemFromRecording(recording.size() -1);
307+
RemoveItemFromRecording(recording.size() - 1);
219308
}
220309

221310
/**

0 commit comments

Comments
 (0)