diff --git a/CONFIG.md b/CONFIG.md
index 2b2f2c8..50dda2d 100644
--- a/CONFIG.md
+++ b/CONFIG.md
@@ -4,51 +4,77 @@ The Visual Studio extension for Force Feedback Programming provides visual plus
Since not all code is created equal and your team's coding style might differ from that of another team you can configure the extension's "sensitivity" and its "use of force".
-As the metric to determine how clean/dirty your code is the extension (currently only) uses the number of lines of a method.
+Currently the extension uses the *number of lines* in a function/methode as its only metric to determine how clean/dirty your code is. We know this might sound simplistic, but we've thought long and hard about it and believe that more sophisticated metrics would only deliver pseudo accuracy.
-This method consists of 10 lines of code starting from the initial `{` and ending with `}`. A background color is chosen accordingly:
+The following method consists of 10 lines of code between the initial `{` and the final `}`. A background color is chosen accordingly:

-Adding a line (by breaking the last statement apart) pushes it into the next "brownfield category", though, and the background color changes:
+Now, adding a line (by breaking the last statement apart) pushes it into the next "brownfield category", though, and the background color changes:

-## config.json
+The visual feedback is immediate, as you see. So is the tactile feedback which consist of spurts of random characters inserted while you type and delaying the effect of your keystrokes.
-Currently the information about the "brownfield categories" is stored in a global JSON config file _.forcefeedbackprogramming_ located at `c:\ProgramData`:
+The metric is simple, as you see, but how is the feedback chosen? That's what you can control with FFP config files:
-
+## Locating the config file
+The rules to assess methods and define the feedback to give are read from a config file with the name `.forcefeedbackprogramming`.
-A [sample config file](example/.forcefeedbackprogramming) is stored next to this documentation in the repo. It's structure is simple:
+The FFP extension will use the first config file it finds while searching for it in a number of places in this order:
+
+1. directory of current source file
+2. directory of project the source file belongs to
+3. directory of solution the project belongs to
+
+If no config file is found, one is created from a default in the solution directory.
+
+## Structure of config file
+A config file is a JSON file with a simple structure. Here's an excerpt from the default config:
```
{
- "methodTooLongLimits": [
+ "Version": "1.0",
+
+ "FeedbackRules": [
{
- "lines": 6,
- "color": "#ffffe5",
- "transparency": 0.25
+ "MinimumNumberOfLinesInMethod": 11,
+
+ "BackgroundColor": "Beige",
+ "BackgroundColorTransparency": 0.0,
+
+ "NoiseDistance": 0,
+ "NoiseLevel": 0,
+
+ "Delay": 0
},
{
- "lines": 11,
- "color": "#fee391",
- "transparency": 0.25,
- "noiseDistance": 10
+ "MinimumNumberOfLinesInMethod": 26,
+
+ "BackgroundColor": "Burlywood",
+ "BackgroundColorTransparency": 0.0,
+
+ "NoiseDistance": 50,
+ "NoiseLevel": 3,
+
+ "Delay": 0
},
...
+ ]
}
```
-The `methodTooLongLimits` list contains entries for each "brownfield category".
-
-In each category you can set the number of lines (`lines`) it starts at. In the example above the first category starts with 6 lines of code in a methode. Once 11 lines of code are reached the second category takes over and so on. That means code with less than 6 lines of code falls into no category. It's not colored. It's considered clean. But from 6 lines of code on code is deemed to grow dirty...
-The `color` property determines how to color a method's background once it falls into a certain category. If you want to adapt the colors to your liking find inspiration (and hex codes) here: [http://www.rapidtables.com/web/color/gray-color.htm](http://www.rapidtables.com/web/color/gray-color.htm)
+Currently it's just a list of rules defining **levels of what's considered clean/dirty**.
-`transparency` determines how "thick" the color is applied. A lower number means less "thickness"/opacity. For most colors/categories a value of 0.25 seems ok.
+* Levels relate to the number of lines in a method only (`MinimumNumberOfLinesInMethod`). The example states that less that 11 lines of code (LOC) is considered perfectly clean. No feedback is given. But from 11 LOC on you'll see/feel feedback. First a little - 11 to 25 LOC -, then more - 26 LOC and up. All methods with more LOC than the highest number in the rules state are covered by that rule.
-So much for the visual feedback. The final value is about the tactile feedback:
+**Visual feedback** is given for each level based on two properties:
-`noiseDistance` determines how often the extension is supposed to add noise to your input. Currently we use the character ⌫ to irritate you. A value of 10 means "every 10 characters you type" you'll see an addition noise character in your code - which you then need to delete, which slows you down, which hopefully motivates your to reduce the number of lines of code in the method to avoid such tactile noise.
+* Visual feedback is given by coloring the backhground of a method's body (`BackgroundColor`). You can define the color by name, e.g. `Beige` or `Maroon` or any color from [this list](http://www.99colors.net/dot-net-colors). Or you can define it with a hex code, e.g. `#F0FFFF` instead of `Azure`; use a [tool like this](https://www.rapidtables.com/web/color/RGB_Color.html) to mix your color.
+* Some colors might lead to bad contrast for the source text when applied in their default "strength" or "thickness". To adjust for better readability you can change the transparency with which they are applied (`BackgroundColorTransparency`). The default is `0.0`, i.e. no transparency/"full strength"/maximum opacity. Choose a value between `0.0` and `1.0` to make the background color more light, more translucent.
+For **tactile feedback** you can tweak three properties:
+* If keystrokes/changes should be delayed (to simulate wading through a brownfield), then set `Delay` to a millisecond value of your choice, e.g. `100` to delay every keystroke by 0.1 seconds.
+* In addition you can add "noise" to the input. That means additional random characters will be inserted to hamper typing progress. `NoiseDistance` defines how many keystrokes/changes should pass without disturbance, e.g. `10` for "insert noise every 10 keystrokes".
+* How many noise characters are inserted is defined by `NoiseLevel`, e.g. `3` to insert a random string like "♥☺❉".
diff --git a/README.md b/README.md
index 153ad02..dd40120 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,14 @@ And not only will FFP tell you how clean your code is, it will actively hinder y
And all this is made possible through a simple Visual Studio extension you can even [adapt to your coding style](CONFIG.md) and level of brownfield.
+Take as an example this screenshot from [a video by contributor Robin](https://vimeo.com/171889390) where he's explaining how FFP works:
+
+
+
+As you see, methods in the source code are colored differently according to how clean/dirty FFP deems them. But this color is not static! It will change while you're editing your code because with each keystroke it might become more or less clean.
+
+That's not all, however. In addition to this kind of visual feedback the FFP extension gives tactile feedback. [Read Robin's article](https://robinsedlaczek.com/2016/06/23/introducing-force-feedback-programming/) and watch his video to see that in action.
+
Enjoy!
PS: If you like what you see and have time to spare, you can join us in moving the FFP extension forward. Please [check the wiki](https://github.com/robinsedlaczek/ForceFeedbackProgramming/wiki) for details.
@@ -24,17 +32,17 @@ You can download the precompiled Visual Studio installer package from the [relea
### Microsoft Visual Studio
-Force Feedback is currently available in Visual Studio only (exactly for VS 2015, 2017 and 2019). Therefore, we deliver a Visual Studio extension that can be found in the VS marketplace [here](https://marketplace.visualstudio.com/items?itemName=RobinSedlaczek.ForceFeedback) and that can be installed via the main menu entry "Tools\Manage Extensions". We update the extension in the marketplace with every stable version. Stable versions on the [releases page](https://github.com/robinsedlaczek/ForceFeedbackProgramming/releases/ "Visual Studio Installer Package releases") are those versions, that are free of any additional version status info (e.g. -alpha, -beta etc.).
+Force Feedback Programming is currently available for C# in Visual Studio only (VS 2015, 2017 and 2019). It's delivered as a Visual Studio extension that can be found in the VS marketplace [here](https://marketplace.visualstudio.com/items?itemName=RobinSedlaczek.ForceFeedback) and that can be installed via the main menu entry "Tools|Manage Extensions". We update the extension in the marketplace with every stable version. Stable versions on the [releases page](https://github.com/robinsedlaczek/ForceFeedbackProgramming/releases/ "Visual Studio Installer Package releases") are those versions, which are free of any additional version status info (e.g. -alpha, -beta etc.).
### ABAB
-The phrase "Visual Studio only" is not really correct. There are some guys who ported the Force Feedback Programming to SAP's [ABAB](https://en.wikipedia.org/wiki/ABAP). We got the hint via Twitter [here](https://twitter.com/ceedee666/status/1106887766221180929). You can find the GitHub repo for the ABAB implementation [here](https://github.com/css-ch/abap-code-feedback).
+To be honest, the phrase "Visual Studio only" might not really be correct anymore. It seems there are some guys who have taken the Force Feedback Programming idea to SAP's [ABAB](https://en.wikipedia.org/wiki/ABAP). [Here's a Tweet](https://twitter.com/ceedee666/status/1106887766221180929) hinting at that. A repo for their ABAB implementation [ABAB implementation](https://github.com/css-ch/abap-code-feedback) is on GitHub.
-[!Please be aware that their implementation is no fork of our repo. The guys there are working independent from us currently. Their ABAB AiE integration is not part of our project and we are not responsible. In case of issues and/or questions, please contact them directly.]
+*Please note: Their implementation is no fork of our repo. It's an independent project. Their ABAB AiE integration is not part of our effort here.*
### Roadmap
-We have the integration for Visual Studio Code and JetBrains Rider on the list!
+We have the integration for Visual Studio Code and JetBrains Rider on our list!
## Health of master (Release|x86):
@@ -42,4 +50,4 @@ We have the integration for Visual Studio Code and JetBrains Rider on the list!
## Wanna chat with us?
-You can meet us here: [](https://gitter.im/robinsedlaczek/ForceFeedbackProgramming?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
\ No newline at end of file
+You can meet us here: [](https://gitter.im/robinsedlaczek/ForceFeedbackProgramming?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
diff --git a/images/example.png b/images/example.png
new file mode 100644
index 0000000..4f01a69
Binary files /dev/null and b/images/example.png differ
diff --git a/src/.idea/.idea.ForceFeedback.Core/.idea/contentModel.xml b/src/.idea/.idea.ForceFeedback.Core/.idea/contentModel.xml
index 83f036d..abd4ca1 100644
--- a/src/.idea/.idea.ForceFeedback.Core/.idea/contentModel.xml
+++ b/src/.idea/.idea.ForceFeedback.Core/.idea/contentModel.xml
@@ -17,6 +17,7 @@
+
@@ -54,8 +55,10 @@
+
+
@@ -68,6 +71,7 @@
+
diff --git a/src/ForceFeedback.Core.Tests_dnc/ConfigrationProvider_tests.cs b/src/ForceFeedback.Core.Tests_dnc/ConfigrationProvider_tests.cs
index 3509f48..a069926 100644
--- a/src/ForceFeedback.Core.Tests_dnc/ConfigrationProvider_tests.cs
+++ b/src/ForceFeedback.Core.Tests_dnc/ConfigrationProvider_tests.cs
@@ -1,3 +1,5 @@
+using System;
+using System.Diagnostics;
using System.Drawing;
using System.IO;
using ForceFeedback.Core.adapters.configuration;
@@ -11,6 +13,9 @@ namespace ForceFeedback.Core.Tests_dnc
*/
public class ConfigrationProvider_tests
{
+ private readonly string TEST_SLN_PATH = "slnfolder-" + Guid.NewGuid().ToString();
+
+
[Fact]
public void Load_default_config_if_no_paths_are_given() {
var sut = new ConfigurationProvider("", "", "");
@@ -24,12 +29,12 @@ public void Load_default_config_if_no_paths_are_given() {
[Fact]
public void Load_config_from_solution_path()
{
- if (Directory.Exists("slnfolder_2"))
- Directory.Delete("slnfolder_2", true);
- Directory.CreateDirectory("slnfolder_2");
- File.Copy("../../../sampledata/ConfigurationDefaults.json", "slnfolder_2/.forcefeedbackprogramming");
+ if (Directory.Exists(TEST_SLN_PATH))
+ Directory.Delete(TEST_SLN_PATH, true);
+ Directory.CreateDirectory(TEST_SLN_PATH);
+ File.Copy("../../../sampledata/ConfigurationDefaults.json", Path.Combine(TEST_SLN_PATH,".forcefeedbackprogramming"));
- var sut = new ConfigurationProvider("slnfolder_2/my.sln", "", "");
+ var sut = new ConfigurationProvider(Path.Combine(TEST_SLN_PATH, "my.sln"), "", "");
var result = sut.Configuration;
Assert.True(result.TryFindRule(1, out var rule));
@@ -40,15 +45,15 @@ public void Load_config_from_solution_path()
[Fact]
public void Create_config_in_sln_folder_from_defaults_if_none_found()
{
- if (Directory.Exists("slnfolder_3"))
- Directory.Delete("slnfolder_3", true);
- Directory.CreateDirectory("slnfolder_3");
+ if (Directory.Exists(TEST_SLN_PATH))
+ Directory.Delete(TEST_SLN_PATH, true);
+ Directory.CreateDirectory(TEST_SLN_PATH);
- var sut = new ConfigurationProvider("slnfolder_3/my.sln", "", "");
+ var sut = new ConfigurationProvider(Path.Combine(TEST_SLN_PATH, "my.sln"), "", "");
var result = sut.Configuration;
Assert.True(result.TryFindRule(30, out var rule));
- Assert.True(File.Exists("slnfolder_3/.forcefeedbackprogramming"));
+ Assert.True(File.Exists(Path.Combine(TEST_SLN_PATH, ".forcefeedbackprogramming")));
Assert.Equal(26, rule.MinimumNumberOfLinesInMethod);
Assert.Equal(Color.BurlyWood, rule.BackgroundColor);
}
@@ -57,14 +62,16 @@ public void Create_config_in_sln_folder_from_defaults_if_none_found()
[Fact]
public void Pick_first_config_found_from_file_path()
{
- if (Directory.Exists("slnfolder_4"))
- Directory.Delete("slnfolder_4", true);
- Directory.CreateDirectory("slnfolder_4/prjfolder/filefolder");
+ if (Directory.Exists(TEST_SLN_PATH))
+ Directory.Delete(TEST_SLN_PATH, true);
+ Directory.CreateDirectory(Path.Combine(TEST_SLN_PATH, "prjfolder", "filefolder"));
- File.Copy("../../../sampledata/ConfigurationDefaults.json", "slnfolder_4/prjfolder/filefolder/.forcefeedbackprogramming");
- File.Copy("../../../sampledata/ConfigurationEmpty.json", "slnfolder_4/prjfolder/.forcefeedbackprogramming");
+ File.Copy("../../../sampledata/ConfigurationDefaults.json", Path.Combine(TEST_SLN_PATH, "prjfolder/filefolder/.forcefeedbackprogramming"));
+ File.Copy("../../../sampledata/ConfigurationEmpty.json", Path.Combine(TEST_SLN_PATH, "prjfolder/.forcefeedbackprogramming"));
- var sut = new ConfigurationProvider("slnfolder_4/my.sln", "slnfolder_4/prjfolder/my.csproj", "slnfolder_4/prjfolder/filefolder/my.cs");
+ var sut = new ConfigurationProvider(Path.Combine(TEST_SLN_PATH, "my.sln"),
+ Path.Combine(TEST_SLN_PATH, "prjfolder/my.csproj"),
+ Path.Combine(TEST_SLN_PATH, "prjfolder/filefolder/my.cs"));
var result = sut.Configuration;
Assert.True(result.TryFindRule(1, out var rule));
@@ -75,18 +82,20 @@ public void Pick_first_config_found_from_file_path()
[Fact]
public void Pick_first_config_found_from_project_path()
{
- if (Directory.Exists("slnfolder_5"))
- Directory.Delete("slnfolder_5", true);
- Directory.CreateDirectory("slnfolder_5/prjfolder/filefolder");
+ if (Directory.Exists(TEST_SLN_PATH))
+ Directory.Delete(TEST_SLN_PATH, true);
+ Directory.CreateDirectory(Path.Combine(TEST_SLN_PATH, "prjfolder", "filefolder"));
- File.Copy("../../../sampledata/ConfigurationDefaults.json", "slnfolder_5/prjfolder/.forcefeedbackprogramming");
- File.Copy("../../../sampledata/ConfigurationEmpty.json", "slnfolder_5/.forcefeedbackprogramming");
+ File.Copy("../../../sampledata/ConfigurationDefaults.json", Path.Combine(TEST_SLN_PATH, "prjfolder/.forcefeedbackprogramming"));
+ File.Copy("../../../sampledata/ConfigurationEmpty.json", Path.Combine(TEST_SLN_PATH, ".forcefeedbackprogramming"));
- var sut = new ConfigurationProvider("slnfolder_5/my.sln", "slnfolder_5/prjfolder/my.csproj", "slnfolder_5/prjfolder/filefolder/my.cs");
+ var sut = new ConfigurationProvider(Path.Combine(TEST_SLN_PATH, "my.sln"),
+ Path.Combine(TEST_SLN_PATH, "prjfolder/my.csproj"),
+ Path.Combine(TEST_SLN_PATH, "prjfolder/filefolder/my.cs"));
var result = sut.Configuration;
Assert.True(result.TryFindRule(1, out var rule));
Assert.Equal(Color.Beige, rule.BackgroundColor);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ForceFeedback.Core.Tests_dnc/DebugFact.cs b/src/ForceFeedback.Core.Tests_dnc/DebugFact.cs
new file mode 100644
index 0000000..f502983
--- /dev/null
+++ b/src/ForceFeedback.Core.Tests_dnc/DebugFact.cs
@@ -0,0 +1,17 @@
+using System.Diagnostics;
+using Xunit;
+
+namespace ForceFeedback.Core.Tests_dnc
+{
+ // Original source and idea: https://lostechies.com/jimmybogard/2013/06/20/run-tests-explicitly-in-xunit-net/
+ internal class DebugFact : FactAttribute
+ {
+ public DebugFact()
+ {
+ if (!Debugger.IsAttached)
+ {
+ Skip = "Only running in interactive mode with debugger attached.";
+ }
+ }
+ }
+}
diff --git a/src/ForceFeedback.Core.Tests_dnc/ForceFeedback.Core.Tests_dnc.csproj b/src/ForceFeedback.Core.Tests_dnc/ForceFeedback.Core.Tests_dnc.csproj
index 7945e4a..3e74151 100644
--- a/src/ForceFeedback.Core.Tests_dnc/ForceFeedback.Core.Tests_dnc.csproj
+++ b/src/ForceFeedback.Core.Tests_dnc/ForceFeedback.Core.Tests_dnc.csproj
@@ -23,6 +23,7 @@
+
diff --git a/src/ForceFeedback.Core.Tests_dnc/ForceFeedbackMachine_tests.cs b/src/ForceFeedback.Core.Tests_dnc/ForceFeedbackMachine_tests.cs
index d25d8fd..6aced73 100644
--- a/src/ForceFeedback.Core.Tests_dnc/ForceFeedbackMachine_tests.cs
+++ b/src/ForceFeedback.Core.Tests_dnc/ForceFeedbackMachine_tests.cs
@@ -1,4 +1,6 @@
-using System.Drawing;
+using System;
+using System.Diagnostics;
+using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
@@ -112,15 +114,44 @@ Noise[] Get_feedback(string methodName)
[Fact]
public void No_feedback_from_empty_config()
{
- if (Directory.Exists("slnfolder_1"))
- Directory.Delete("slnfolder_1", true);
- Directory.CreateDirectory("slnfolder_1");
- File.Copy("../../../sampledata/ConfigurationEmpty.json", "slnfolder_1/.forcefeedbackprogramming");
+ var TEST_SLN_FOLDER = "slnfolder-" + Guid.NewGuid().ToString();
- var sut = new ForceFeedbackMachine("slnfolder_1/my.sln", "", "");
+ if (Directory.Exists(TEST_SLN_FOLDER)) Directory.Delete(TEST_SLN_FOLDER, true);
+ Directory.CreateDirectory(TEST_SLN_FOLDER);
+ File.Copy("../../../sampledata/ConfigurationEmpty.json", Path.Combine(TEST_SLN_FOLDER, ".forcefeedbackprogramming"));
+
+ var sut = new ForceFeedbackMachine(Path.Combine(TEST_SLN_FOLDER, "my.sln"), "", "");
var result = sut.ProduceVisualFeedback("Foo", 999);
Assert.Empty(result);
}
+
+
+ [DebugFact]
+ public void Provoke_exception_during_feedback()
+ {
+ var GLOBAL_LOG_PATH = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
+ var logFilename = Path.Combine(GLOBAL_LOG_PATH, ".forcefeedbackprogramming.log");
+ DeleteLogfile();
+
+ var TEST_SLN_FOLDER = "slnfolder-" + Guid.NewGuid().ToString();
+
+ if (Directory.Exists(TEST_SLN_FOLDER)) Directory.Delete(TEST_SLN_FOLDER, true);
+ Directory.CreateDirectory(TEST_SLN_FOLDER);
+ File.Copy("../../../sampledata/ConfigurationWithError.json", Path.Combine(TEST_SLN_FOLDER, ".forcefeedbackprogramming"));
+
+ try
+ {
+ new ForceFeedbackMachine(Path.Combine(TEST_SLN_FOLDER, "my.sln"), "", "");
+
+ Assert.True(File.Exists(logFilename));
+ }
+ finally
+ {
+ DeleteLogfile();
+ }
+
+ void DeleteLogfile() { if (File.Exists(logFilename)) File.Delete(logFilename); }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/ForceFeedback.Core.Tests_dnc/Log_tests.cs b/src/ForceFeedback.Core.Tests_dnc/Log_tests.cs
new file mode 100644
index 0000000..6f00092
--- /dev/null
+++ b/src/ForceFeedback.Core.Tests_dnc/Log_tests.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using ForceFeedback.Core.adapters;
+using Xunit;
+
+namespace ForceFeedback.Core.Tests_dnc
+{
+ public class Log_tests
+ {
+ private readonly string TEST_LOG_PATH = "logtest-" + Guid.NewGuid().ToString();
+
+
+ [Fact]
+ public void Append()
+ {
+ if (Directory.Exists(TEST_LOG_PATH)) Directory.Delete(TEST_LOG_PATH, true);
+ Directory.CreateDirectory(TEST_LOG_PATH);
+
+ var sut = new Log(TEST_LOG_PATH);
+ sut.Append("foo");
+
+ var result = File.ReadAllLines(Path.Combine(TEST_LOG_PATH,".forcefeedbackprogramming.log"));
+ Assert.Equal(3, result.Length);
+ Assert.Equal("foo", result[1]);
+ }
+
+
+ [Fact]
+ public void Try_no_error() {
+ if (Directory.Exists(TEST_LOG_PATH)) Directory.Delete(TEST_LOG_PATH, true);
+ Directory.CreateDirectory(TEST_LOG_PATH);
+
+ var sut = new Log(TEST_LOG_PATH);
+ sut.Try(
+ () => Console.WriteLine("No exception!"),
+ null);
+
+ Assert.False(File.Exists(Path.Combine(TEST_LOG_PATH,".forcefeedbackprogramming.log")));
+ }
+
+
+ [Fact]
+ public void Try_with_error() {
+ if (Directory.Exists(TEST_LOG_PATH)) Directory.Delete(TEST_LOG_PATH, true);
+ Directory.CreateDirectory(TEST_LOG_PATH);
+
+ var result = false;
+ var sut = new Log(TEST_LOG_PATH);
+ sut.Try(
+ () => throw new ApplicationException(),
+ () => result = true);
+
+ Assert.True(result);
+ var logText = File.ReadAllText(Path.Combine(TEST_LOG_PATH,".forcefeedbackprogramming.log"));
+ Debug.WriteLine($"<<>>>>>");
+ Assert.True(logText.IndexOf("ApplicationException") > 0);
+ }
+
+
+ [DebugFact()] // Run manually in debug mode; this is to avoid automatic global folder "pollution".
+ public void Try_with_global_log_folder()
+ {
+ var GLOBAL_LOG_PATH = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
+ var logFilename = Path.Combine(GLOBAL_LOG_PATH, ".forcefeedbackprogramming.log");
+ DeleteLogfile();
+
+ try
+ {
+ var sut = new Log(GLOBAL_LOG_PATH);
+ sut.Append("foo");
+
+ var logText = File.ReadAllText(logFilename);
+ Assert.True(logText.IndexOf("foo") > 0);
+ }
+ finally
+ {
+ DeleteLogfile();
+ }
+
+
+ void DeleteLogfile() { if (File.Exists(logFilename)) File.Delete(logFilename); }
+ }
+ }
+}
diff --git a/src/ForceFeedback.Core.Tests_dnc/sampledata/ConfigurationWithError.json b/src/ForceFeedback.Core.Tests_dnc/sampledata/ConfigurationWithError.json
new file mode 100644
index 0000000..50606dc
--- /dev/null
+++ b/src/ForceFeedback.Core.Tests_dnc/sampledata/ConfigurationWithError.json
@@ -0,0 +1,16 @@
+{
+ "Version": "1.0",
+
+ "FeedbackRules": [
+ {
+ "MinimumNumberOfLinesInMethod": 1,
+
+ // missing properties and wrongly formatted JSON
+
+ "NoiseDistance": 3,
+ "NoiseLevel": 4,
+
+ "Delay": 5
+ }
+ ]
+}
diff --git a/src/ForceFeedback.Core/ForceFeedbackMachine.cs b/src/ForceFeedback.Core/ForceFeedbackMachine.cs
index 9204549..329f7fc 100644
--- a/src/ForceFeedback.Core/ForceFeedbackMachine.cs
+++ b/src/ForceFeedback.Core/ForceFeedbackMachine.cs
@@ -1,7 +1,12 @@
-using System.Collections.Generic;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Drawing;
+using ForceFeedback.Core.adapters;
using ForceFeedback.Core.adapters.configuration;
using ForceFeedback.Core.domain;
using ForceFeedback.Core.Feedback;
+using ForceFeedback.Core.Feedback.Visual;
namespace ForceFeedback.Core
{
@@ -22,14 +27,34 @@ internal ForceFeedbackMachine(Configuration config)
public IEnumerable ProduceVisualFeedback(string methodName, int methodLineCount)
- => _config.TryFindRule(methodLineCount, out var rule)
+ => Try(() =>
+ _config.TryFindRule(methodLineCount, out var rule)
? _feedbackGen.Visual_feedback(rule)
- : _feedbackGen.No_feedback;
+ : _feedbackGen.No_feedback
+ );
public IEnumerable ProduceTotalFeedback(string methodName, int methodLineCount)
- => _config.TryFindRule(methodLineCount, out var rule)
- ? _feedbackGen.Total_feedback(methodName, rule)
- : _feedbackGen.No_feedback;
+ => Try(() =>
+ _config.TryFindRule(methodLineCount, out var rule)
+ ? _feedbackGen.Total_feedback(methodName, rule)
+ : _feedbackGen.No_feedback
+ );
+
+
+ private IEnumerable Try(Func> generateFeedback)
+ {
+ IEnumerable feedback = new IFeedback[0];
+ new Log().Try(
+ () =>
+ {
+ feedback = generateFeedback();
+ },
+ () =>
+ {
+ feedback = new[] {new Colorization(Color.Red, 1.0)};
+ });
+ return feedback;
+ }
}
}
diff --git a/src/ForceFeedback.Core/adapters/Log.cs b/src/ForceFeedback.Core/adapters/Log.cs
new file mode 100644
index 0000000..60d3fa4
--- /dev/null
+++ b/src/ForceFeedback.Core/adapters/Log.cs
@@ -0,0 +1,34 @@
+using System;
+using System.IO;
+
+namespace ForceFeedback.Core.adapters
+{
+ public class Log
+ {
+ private readonly string _logPath;
+
+ public Log() : this(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)) {}
+ public Log(string logPath) => _logPath = logPath;
+
+
+ public void Try(Action run, Action onFailure)
+ {
+ try
+ {
+ run();
+ }
+ catch (Exception ex)
+ {
+ Append(ex.ToString());
+ onFailure();
+ }
+ }
+
+ public void Append(string message)
+ {
+ var logFilePath = Path.Combine(_logPath, ".forcefeedbackprogramming.log");
+ var entryText = new[] {$"---{DateTime.Now.ToString()}---", message, ""};
+ File.AppendAllLines(logFilePath, entryText);
+ }
+ }
+}
diff --git a/src/ForceFeedback.Core/adapters/configuration/ConfigurationProvider.cs b/src/ForceFeedback.Core/adapters/configuration/ConfigurationProvider.cs
index 1f331a8..fb8561a 100644
--- a/src/ForceFeedback.Core/adapters/configuration/ConfigurationProvider.cs
+++ b/src/ForceFeedback.Core/adapters/configuration/ConfigurationProvider.cs
@@ -10,32 +10,40 @@ namespace ForceFeedback.Core.adapters.configuration
{
class ConfigurationProvider
{
+ private Configuration _config;
private const string DEFAUL_CONFIG_FILENAME = ".forcefeedbackprogramming";
public ConfigurationProvider(string solutionFilePath, string projectFilePath, string sourceFilePath)
{
- var solutionFolderPath = Path.GetDirectoryName(solutionFilePath);
- var projectFolderPath = Path.GetDirectoryName(projectFilePath);
- var sourceFolderPath = Path.GetDirectoryName(sourceFilePath);
-
- /*
- * Find config file in one of the paths provided - or create it from defaults in the solution path.
- */
- if (Try_to_find_config_file(solutionFolderPath, projectFolderPath, sourceFolderPath, out var configFilePath))
- {
- var configText = File.ReadAllText(configFilePath);
- Configuration = Deserialize_configuration(configText);
- }
- else
- {
- var configText = ConfigurationDefaultLoader.Load_default_configuration_text();
- Create_config_file(solutionFolderPath, configText);
- Configuration = Deserialize_configuration(configText);
- }
+ new Log().Try(
+ () =>
+ {
+ var solutionFolderPath = Path.GetDirectoryName(solutionFilePath);
+ var projectFolderPath = Path.GetDirectoryName(projectFilePath);
+ var sourceFolderPath = Path.GetDirectoryName(sourceFilePath);
+
+ /*
+ * Find config file in one of the paths provided - or create it from defaults in the solution path.
+ */
+ if (Try_to_find_config_file(solutionFolderPath, projectFolderPath, sourceFolderPath,
+ out var configFilePath))
+ {
+ var configText = File.ReadAllText(configFilePath);
+ _config = Deserialize_configuration(configText);
+ }
+ else
+ {
+ var configText = ConfigurationDefaultLoader.Load_default_configuration_text();
+ Create_config_file(solutionFolderPath, configText);
+ _config = Deserialize_configuration(configText);
+ }
+ },
+ () => { }
+ );
}
- public Configuration Configuration { get; }
+ public Configuration Configuration => _config;
private static bool Try_to_find_config_file(string solutionFolderPath, string projectFolderPath, string sourceFolderPath,