Browse Source

Code formatting

tags/v2.0.0
Cédric Belin 4 months ago
parent
commit
817eb9c846
19 changed files with 610 additions and 608 deletions
  1. +6
    -3
      .editorconfig
  2. +3
    -5
      .github/workflows/build.yaml
  3. +5
    -5
      .vscode/settings.json
  4. +1
    -1
      README.md
  5. +1
    -1
      analysis_options.yaml
  6. +0
    -3
      doc/about/see_also.md
  7. +4
    -4
      doc/index.md
  8. +8
    -8
      doc/installation.md
  9. +91
    -91
      doc/usage/api.md
  10. +36
    -36
      doc/usage/events.md
  11. +9
    -5
      etc/mkdocs.yaml
  12. +41
    -41
      example/main.dart
  13. +11
    -11
      lib/src/simple_change.dart
  14. +129
    -129
      lib/src/storage.dart
  15. +8
    -8
      lib/webstorage.dart
  16. +1
    -1
      pubspec.yaml
  17. +29
    -29
      test/simple_change_test.dart
  18. +224
    -224
      test/storage_test.dart
  19. +3
    -3
      tool/clean.ps1

+ 6
- 3
.editorconfig View File

@@ -3,11 +3,14 @@ root = true

[*]
charset = utf-8
indent_size = 2
indent_style = space
indent_style = tab
insert_final_newline = true
tab_width = 2
trim_trailing_whitespace = true

[*.md]
indent_size = 4
trim_trailing_whitespace = false

[*.{yaml,yml}]
indent_size = 2
indent_style = space

+ 3
- 5
.github/workflows/build.yaml View File

@@ -3,13 +3,13 @@ on:
pull_request:
push:
schedule:
- cron: '0 0 1 * *'
- cron: "0 0 1 * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Set up Dart
uses: cedx/setup-dart@v1
uses: cedx/setup-dart@v2
- name: Check environment
run: |
dart --version
@@ -19,9 +19,7 @@ jobs:
- name: Install dependencies
run: pub get
- name: Run tests
uses: GabrielBB/xvfb-action@v1.0
with:
run: pub run build_runner test --delete-conflicting-outputs -- --coverage=var
run: pub run build_runner test --delete-conflicting-outputs -- --coverage=var
- name: Collect code coverage
env:
COVERALLS_REPO_TOKEN: ${{secrets.GITHUB_TOKEN}}


+ 5
- 5
.vscode/settings.json View File

@@ -1,7 +1,7 @@
{
"editor.insertSpaces": true,
"editor.tabSize": 2,
"files.encoding": "utf8",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true
"editor.insertSpaces": false,
"editor.tabSize": 2,
"files.encoding": "utf8",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true
}

+ 1
- 1
README.md View File

@@ -1,5 +1,5 @@
# Web Storage for Dart
![Runtime](https://badgen.net/badge/dart/%3E%3D2.8.0/green) ![Release](https://img.shields.io/pub/v/webstorage.svg) ![License](https://badgen.net/badge/license/MIT/blue) ![Coverage](https://badgen.net/coveralls/c/github/cedx/webstorage.dart) ![Build](https://badgen.net/github/checks/cedx/webstorage.dart)
![Runtime](https://badgen.net/pub/sdk-version/webstorage) ![Release](https://badgen.net/pub/v/webstorage) ![License](https://badgen.net/pub/license/webstorage) ![Likes](https://badgen.net/pub/likes/webstorage) ![Coverage](https://badgen.net/coveralls/c/github/cedx/webstorage.dart) ![Build](https://badgen.net/github/checks/cedx/webstorage.dart)

Services for interacting with the [Web Storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage), in [Dart](https://dart.dev).



+ 1
- 1
analysis_options.yaml View File

@@ -88,6 +88,7 @@ linter:
- prefer_const_literals_to_create_immutables
- prefer_constructors_over_static_methods
- prefer_contains
- prefer_double_quotes
- prefer_equal_for_default_values
- prefer_expression_function_bodies
- prefer_final_fields
@@ -110,7 +111,6 @@ linter:
- prefer_mixin
- prefer_null_aware_operators
- prefer_relative_imports
- prefer_single_quotes
- prefer_spread_collections
- prefer_typing_uninitialized_variables
- prefer_void_to_null


+ 0
- 3
doc/about/see_also.md View File

@@ -7,6 +7,3 @@

## Testing
- [Continuous integration](https://github.com/cedx/webstorage.dart/actions)

## Other implementations
- JavaScript: [Web Storage for JS](https://docs.belin.io/webstorage.js)

+ 4
- 4
doc/index.md View File

@@ -1,19 +1,19 @@
# Web Storage <small>for Dart</small>
![Runtime](https://badgen.net/badge/dart/%3E%3D2.8.0/green) ![Release](https://img.shields.io/pub/v/webstorage.svg) ![License](https://badgen.net/badge/license/MIT/blue) ![Coverage](https://badgen.net/coveralls/c/github/cedx/webstorage.dart) ![Build](https://badgen.net/github/checks/cedx/webstorage.dart)
![Runtime](https://badgen.net/pub/sdk-version/webstorage) ![Release](https://badgen.net/pub/v/webstorage) ![License](https://badgen.net/pub/license/webstorage) ![Likes](https://badgen.net/pub/likes/webstorage) ![Coverage](https://badgen.net/coveralls/c/github/cedx/webstorage.dart) ![Build](https://badgen.net/github/checks/cedx/webstorage.dart)

Services for interacting with the [Web Storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage), in [Dart](https://dart.dev).

## Quick start
Append the following line to your project's `pubspec.yaml` file:

```yaml
``` yaml
dependencies:
webstorage: *
webstorage: *
```

Install the latest version of **Web Storage for Dart** with [Pub](https://dart.dev/tools/pub):

```shell
``` shell
pub get
```



+ 8
- 8
doc/installation.md View File

@@ -6,7 +6,7 @@ and [Pub](https://dart.dev/tools/pub), the Dart package manager, up and running.

You can verify if you're already good to go with the following commands:

```shell
``` shell
dart --version
# Dart VM version: 2.8.1 (stable) (Thu Apr 30 09:25:21 2020 +0200) on "windows_x64"

@@ -15,31 +15,31 @@ pub --version
```

!!! info
If you plan to play with the package sources, you will also need
[PowerShell](https://docs.microsoft.com/en-us/powershell) and [Material for MkDocs](https://squidfunk.github.io/mkdocs-material).
If you plan to play with the package sources, you will also need
[PowerShell](https://docs.microsoft.com/en-us/powershell) and [Material for MkDocs](https://squidfunk.github.io/mkdocs-material).

## Installing with Pub package manager

### 1. Depend on it
Add this to your project's `pubspec.yaml` file:

```yaml
``` yaml
dependencies:
webstorage: *
webstorage: *
```

### 2. Install it
Install this package and its dependencies from a command prompt:

```shell
``` shell
pub get
```

### 3. Import it
Now in your [Dart](https://dart.dev) code, you can use:

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";
```

See the [usage information](usage/api.md).

+ 91
- 91
doc/usage/api.md View File

@@ -6,17 +6,17 @@ source: lib/src/storage.dart
# Programming interface
This package provides two services dedicated to the Web Storage: the `LocalStorage` and `SessionStorage` classes.

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";

void main() {
final storage = LocalStorage();
final storage = LocalStorage();

storage['foo'] = 'bar';
print(storage['foo']); // "bar"
storage["foo"] = "bar";
print(storage["foo"]); // "bar"

storage.setObject('foo', <String, String>{'baz': 'qux'});
print(storage.getObject('foo')); // {"baz": "qux"}
storage.setObject("foo", <String, String>{"baz": "qux"});
print(storage.getObject("foo")); // {"baz": "qux"}
}
```

@@ -25,62 +25,62 @@ Each class extends from the `WebStorage` abstract class that implements the [`Ma
## Iterable&lt;String&gt; get **keys**
Returns the keys of the the associated storage:

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";

void main() {
final storage = LocalStorage();
print(storage.keys); // []
storage['foo'] = 'bar';
print(storage.keys); // ["foo"]
final storage = LocalStorage();
print(storage.keys); // []
storage["foo"] = "bar";
print(storage.keys); // ["foo"]
}
```

## int get **length**
Returns the number of entries in the associated storage:

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";

void main() {
final storage = LocalStorage();
print(storage.length); // 0
storage['foo'] = 'bar';
print(storage.length); // 1
final storage = LocalStorage();
print(storage.length); // 0
storage["foo"] = "bar";
print(storage.length); // 1
}
```

## void **clear**()
Removes all entries from the associated storage:

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";

void main() {
final storage = LocalStorage();
final storage = LocalStorage();

storage['foo'] = 'bar';
print(storage.length); // 1
storage.clear();
print(storage.length); // 0
storage["foo"] = "bar";
print(storage.length); // 1
storage.clear();
print(storage.length); // 0
}
```

## bool **containsKey**(String key)
Returns a boolean value indicating whether the associated storage contains the specified key:

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";

void main() {
final storage = LocalStorage();
print(storage.containsKey('foo')); // false
storage['foo'] = 'bar';
print(storage.containsKey('foo')); // true
final storage = LocalStorage();
print(storage.containsKey("foo")); // false
storage["foo"] = "bar";
print(storage.containsKey("foo")); // true
}
```

@@ -88,15 +88,15 @@ void main() {
When a service is instantiated, it can listen to the global [storage events](https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event).
When you have done using the service instance, you should call the `destroy()` method to cancel the subscription to these events.

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";

void main() {
// Work with the service...
final storage = LocalStorage(listenToStorageEvents: true);
// Work with the service...
final storage = LocalStorage(listenToStorageEvents: true);

// Later, cancel the subscription to the storage events.
storage.destroy();
// Later, cancel the subscription to the storage events.
storage.destroy();
}
```

@@ -105,16 +105,16 @@ See the [events](events.md) section for more information.
## String **get**(String key, [String defaultValue])
Returns the value associated to the specified key:

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";

void main() {
final storage = LocalStorage();
print(storage.get('foo')); // null
print(storage.get('foo', 'qux')); // "qux"
final storage = LocalStorage();
print(storage.get("foo")); // null
print(storage.get("foo", "qux")); // "qux"

storage['foo'] = 'bar';
print(storage.get('foo')); // "bar"
storage["foo"] = "bar";
print(storage.get("foo")); // "bar"
}
```

@@ -123,21 +123,21 @@ Returns `null` or the given default value if the key is not found.
## dynamic **getObject**(String key, [dynamic defaultValue])
Deserializes and returns the value associated to the specified key:

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";

void main() {
final storage = LocalStorage();
print(storage.getObject('foo')); // null
print(storage.getObject('foo', 'qux')); // "qux"
storage.setObject('foo', <String, String>{'bar': 'baz'});
print(storage.getObject('foo')); // {"bar": "baz"}
final storage = LocalStorage();
print(storage.getObject("foo")); // null
print(storage.getObject("foo", "qux")); // "qux"
storage.setObject("foo", <String, String>{"bar": "baz"});
print(storage.getObject("foo")); // {"bar": "baz"}
}
```

!!! info
The value is deserialized using the [`jsonDecode`](https://api.dart.dev/stable/dart-convert/jsonDecode.html) function.
The value is deserialized using the [`jsonDecode`](https://api.dart.dev/stable/dart-convert/jsonDecode.html) function.

Returns a `null` reference or the given default value if the key is not found.

@@ -146,39 +146,39 @@ Looks up the value of the specified key, or add a new value if it isn't there.

Returns the deserialized value associated to the key, if there is one. Otherwise calls `ifAbsent` to get a new value, serializes and associates the key to that value, and then returns the new value:

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";

void main() {
final storage = LocalStorage();
print(storage.containsKey('foo')); // false
final storage = LocalStorage();
print(storage.containsKey("foo")); // false

var value = storage.putObjectIfAbsent('foo', () => 123);
print(storage.containsKey('foo')); // true
print(value); // 123
var value = storage.putObjectIfAbsent("foo", () => 123);
print(storage.containsKey("foo")); // true
print(value); // 123

value = storage.putObjectIfAbsent('foo', () => 456);
print(value); // 123
value = storage.putObjectIfAbsent("foo", () => 456);
print(value); // 123
}
```

!!! info
The value is serialized using the [`jsonEncode`](https://api.dart.dev/stable/dart-convert/jsonEncode.html) function, and deserialized using the [`jsonDecode`](https://api.dart.dev/stable/dart-convert/jsonDecode.html) function.
The value is serialized using the [`jsonEncode`](https://api.dart.dev/stable/dart-convert/jsonEncode.html) function, and deserialized using the [`jsonDecode`](https://api.dart.dev/stable/dart-convert/jsonDecode.html) function.

## String **remove**(String key)
Removes the value associated to the specified key:

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";

void main() {
final storage = LocalStorage();
final storage = LocalStorage();

storage['foo'] = 'bar';
print(storage.containsKey('foo')); // true
print(storage.remove('foo')); // "bar"
print(storage.containsKey('foo')); // false
storage["foo"] = "bar";
print(storage.containsKey("foo")); // true
print(storage.remove("foo")); // "bar"
print(storage.containsKey("foo")); // false
}
```

@@ -187,32 +187,32 @@ Returns the value associated with the specified key before it was removed.
## void **set**(String key, String value)
Associates a given value to the specified key:

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";

void main() {
final storage = LocalStorage();
print(storage.get('foo')); // null
storage.set('foo', 'bar');
print(storage.get('foo')); // "bar"
final storage = LocalStorage();
print(storage.get("foo")); // null
storage.set("foo", "bar");
print(storage.get("foo")); // "bar"
}
```

## void **setObject**(String key, dynamic value)
Serializes and associates a given value to the specified key:

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";

void main() {
final storage = LocalStorage();
print(storage.getObject('foo')); // null
storage.setObject('foo', <String, String>{'bar': 'baz'});
print(storage.getObject('foo')); // {"bar": "baz"}
final storage = LocalStorage();
print(storage.getObject("foo")); // null
storage.setObject("foo", <String, String>{"bar": "baz"});
print(storage.getObject("foo")); // {"bar": "baz"}
}
```

!!! info
The value is serialized using the [`jsonEncode`](https://api.dart.dev/stable/dart-convert/jsonEncode.html) function.
The value is serialized using the [`jsonEncode`](https://api.dart.dev/stable/dart-convert/jsonEncode.html) function.

+ 36
- 36
doc/usage/events.md View File

@@ -8,46 +8,46 @@ Every time one or several values are changed (added, removed or updated) through

These events are exposed as [`Stream`](https://api.dart.dev/stable/dart-async/Stream-class.html), you can listen to them using the `onChanges` property:

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";

void main() {
LocalStorage().onChanges.listen((changes) {
for (final entry in changes.entries) print('${entry.key}: ${entry.value}');
});
LocalStorage().onChanges.listen((changes) {
for (final entry in changes.entries) print("${entry.key}: ${entry.value}");
});
}
```

The changes are expressed as a [`Map`](https://api.dart.dev/stable/dart-core/Map-class.html) of `SimpleChange` instances, where a `null` property indicates an absence of value:

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";

void main() {
final storage = LocalStorage();
storage.onChanges.listen((changes) {
for (final entry in changes.entries) print({
'key': entry.key,
'current': entry.value.currentValue,
'previous': entry.value.previousValue
});
});
storage['foo'] = 'bar';
// Prints: {"key": "foo", "current": "bar", "previous": null}
storage['foo'] = 'baz';
// Prints: {"key": "foo", "current": "baz", "previous": "bar"}
storage.remove('foo');
// Prints: {"key": "foo", "current": null, "previous": "baz"}
final storage = LocalStorage();
storage.onChanges.listen((changes) {
for (final entry in changes.entries) print({
"key": entry.key,
"current": entry.value.currentValue,
"previous": entry.value.previousValue
});
});
storage["foo"] = "bar";
// Prints: {"key": "foo", "current": "bar", "previous": null}
storage["foo"] = "baz";
// Prints: {"key": "foo", "current": "baz", "previous": "bar"}
storage.remove("foo");
// Prints: {"key": "foo", "current": null, "previous": "baz"}
}
```

The values contained in the `currentValue` and `previousValue` properties of the `SimpleChange` instances are the raw storage values. If you use the `WebStorage.setObject()` method to store a value, you will get the serialized string value, not the original value passed to the method:

```dart
storage.setObject('foo', <String, String>{'bar': 'baz'});
``` dart
storage.setObject("foo", <String, String>{"bar": "baz"});
// Prints: {"key": "foo", "current": "{\"bar\": \"baz\"}", "previous": null}
```

@@ -58,21 +58,21 @@ When a change is made to the storage area within the context of another document

The class constructors have a `listenToStorageEvents` parameter that allows to enable the subscription to the global storage events:

```dart
import 'package:webstorage/webstorage.dart';
``` dart
import "package:webstorage/webstorage.dart";

void main() {
// Enable the subscription to the global events of the local storage.
final storage = LocalStorage(listenToStorageEvents: true);
// Enable the subscription to the global events of the local storage.
final storage = LocalStorage(listenToStorageEvents: true);

storage.onChanges.listen((changes) {
// Also occurs when the local storage is changed in another document.
});
storage.onChanges.listen((changes) {
// Also occurs when the local storage is changed in another document.
});

// Later, cancel the subscription to the storage events.
storage.destroy();
// Later, cancel the subscription to the storage events.
storage.destroy();
}
```

!!! important
When you enable the subscription to the global [storage events](https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event), you must take care to call the `destroy()` method when you have finished with the service in order to avoid a memory leak.
When you enable the subscription to the global [storage events](https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event), you must take care to call the `destroy()` method when you have finished with the service in order to avoid a memory leak.

+ 9
- 5
etc/mkdocs.yaml View File

@@ -8,19 +8,23 @@ site_dir: ../www

repo_name: git.belin.io
repo_url: https://git.belin.io/cedx/webstorage.dart
edit_uri: ''
edit_uri: ""

copyright: Copyright &copy; 2019 - 2020 Cédric Belin
extra:
social:
- icon: fontawesome/solid/globe
link: 'https://belin.io'
link: "https://belin.io"
name: Belin.io
- icon: fontawesome/brands/github
link: 'https://github.com/cedx'
link: "https://github.com/cedx"
name: GitHub
- icon: fontawesome/brands/twitter
link: 'https://twitter.com/cedxbelin'
link: "https://twitter.com/cedxbelin"
name: Twitter
- icon: fontawesome/brands/linkedin
link: 'https://linkedin.com/in/cedxbelin'
link: "https://linkedin.com/in/cedxbelin"
name: LinkedIn

markdown_extensions:
- admonition


+ 41
- 41
example/main.dart View File

@@ -1,46 +1,46 @@
// ignore_for_file: avoid_print
import 'package:webstorage/webstorage.dart';
import "package:webstorage/webstorage.dart";

/// Tests the cookie service.
void main() {
final storage = LocalStorage();
// Query the storage.
print(storage.containsKey('foo')); // false
print(storage.containsKey('baz')); // false
print(storage.isEmpty); // true
print(storage.keys); // []
// Write to the storage.
storage['foo'] = 'bar';
print(storage.containsKey('foo')); // true
print(storage.length); // 1
print(storage.keys); // ["foo"]
storage.setObject('baz', <String, int>{'qux': 123});
print(storage.containsKey('baz')); // true
print(storage.length); // 2
print(storage.keys); // ["foo", "baz"]
// Read the storage.
print(storage['foo'].runtimeType); // "String"
print(storage['foo']); // "bar"
print(storage.getObject('baz').runtimeType); // "_JsonMap"
print(storage.getObject('baz')); // {"qux": 123}
print(storage.getObject('baz')['qux']); // 123
// Delete from the storage.
storage.remove('foo');
print(storage.containsKey('foo')); // false
print(storage.length); // 1
print(storage.keys); // ["baz"]
storage.clear();
print(storage.containsKey('baz')); // false
print(storage.isEmpty); // true
print(storage.keys); // []
// Release the event listeners.
storage.destroy();
final storage = LocalStorage();
// Query the storage.
print(storage.containsKey("foo")); // false
print(storage.containsKey("baz")); // false
print(storage.isEmpty); // true
print(storage.keys); // []
// Write to the storage.
storage["foo"] = "bar";
print(storage.containsKey("foo")); // true
print(storage.length); // 1
print(storage.keys); // ["foo"]
storage.setObject("baz", <String, int>{"qux": 123});
print(storage.containsKey("baz")); // true
print(storage.length); // 2
print(storage.keys); // ["foo", "baz"]
// Read the storage.
print(storage["foo"].runtimeType); // "String"
print(storage["foo"]); // "bar"
print(storage.getObject("baz").runtimeType); // "_JsonMap"
print(storage.getObject("baz")); // {"qux": 123}
print(storage.getObject("baz")["qux"]); // 123
// Delete from the storage.
storage.remove("foo");
print(storage.containsKey("foo")); // false
print(storage.length); // 1
print(storage.keys); // ["baz"]
storage.clear();
print(storage.containsKey("baz")); // false
print(storage.isEmpty); // true
print(storage.keys); // []
// Release the event listeners.
storage.destroy();
}

+ 11
- 11
lib/src/simple_change.dart View File

@@ -1,21 +1,21 @@
part of '../webstorage.dart';
part of "../webstorage.dart";

/// Represents the event parameter used for a change event.
@JsonSerializable()
class SimpleChange {

/// Creates a new simple change.
const SimpleChange({this.currentValue, this.previousValue});
/// Creates a new simple change.
const SimpleChange({this.currentValue, this.previousValue});

/// Creates a new simple change from the specified [map] in JSON format.
factory SimpleChange.fromJson(Map<String, dynamic> map) => _$SimpleChangeFromJson(map);
/// Creates a new simple change from the specified [map] in JSON format.
factory SimpleChange.fromJson(Map<String, dynamic> map) => _$SimpleChangeFromJson(map);

/// The current value, or a `null` reference if removed.
final String currentValue;
/// The current value, or a `null` reference if removed.
final String currentValue;

/// The previous value, or a `null` reference if added.
final String previousValue;
/// The previous value, or a `null` reference if added.
final String previousValue;

/// Converts this object to a [Map] in JSON format.
Map<String, dynamic> toJson() => _$SimpleChangeToJson(this);
/// Converts this object to a [Map] in JSON format.
Map<String, dynamic> toJson() => _$SimpleChangeToJson(this);
}

+ 129
- 129
lib/src/storage.dart View File

@@ -1,144 +1,144 @@
part of '../webstorage.dart';
part of "../webstorage.dart";

/// Provides access to the [Web Storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage).
abstract class WebStorage extends Object with MapMixin<String, String> { // ignore: prefer_mixin

/// Creates a new storage service.
WebStorage(this._backend, {bool listenToStorageEvents = false}) {
if (listenToStorageEvents) _subscription = dom.window.onStorage.listen((event) {
if (event.key == null || event.storageArea != _backend) return;
_onChanges.add(<String, SimpleChange>{
event.key: SimpleChange(previousValue: event.oldValue, currentValue: event.newValue)
});
});
}
/// The underlying data store.
final dom.Storage _backend;
/// The handler of "changes" events.
final StreamController<Map<String, SimpleChange>> _onChanges = StreamController<Map<String, SimpleChange>>.broadcast();
/// The subscription to the storage events.
StreamSubscription<dom.StorageEvent> _subscription;
/// Value indicating whether there is no key/value pair in this storage.
@override
bool get isEmpty => _backend.isEmpty;
/// Value indicating whether there is at least one key/value pair in this storage.
@override
bool get isNotEmpty => _backend.isNotEmpty;
/// The keys of this storage.
@override
Iterable<String> get keys => _backend.keys;
/// The number of key/value pairs in this storage.
@override
int get length => _backend.length;
/// The stream of "changes" events.
Stream<Map<String, SimpleChange>> get onChanges => _onChanges.stream;
/// Gets the value associated to the specified [key].
@override
String operator [](Object key) => get(key);
/// Associates the [key] with the given [value].
@override
void operator []=(String key, String value) => set(key, value);
/// Removes all pairs from this storage.
@override
void clear() {
final changes = <String, SimpleChange>{};
for (final entry in entries) changes[entry.key] = SimpleChange(previousValue: entry.value);
_backend.clear();
_onChanges.add(changes);
}
/// Gets a value indicating whether this storage contains the given [key].
@override
bool containsKey(Object key) {
assert(key is String && key.isNotEmpty);
return _backend.containsKey(key);
}
/// Cancels the subscription to the storage events.
void destroy() => _subscription?.cancel();
/// Applies the specified function to each key/value pair of this storage.
@override
void forEach(void Function(String key, String value) action) => _backend.forEach(action);
/// Gets the value associated to the specified [key].
/// Returns the given default value if the storage item is not found.
String get(String key, [String defaultValue]) => containsKey(key) ? _backend[key] : defaultValue;
/// Gets the deserialized value associated to the specified [key].
/// Returns the given default value if the storage item is not found.
dynamic getObject(String key, [defaultValue]) {
try {
final value = this[key];
return value is String ? jsonDecode(value) : defaultValue;
}
on FormatException {
return defaultValue;
}
}
/// Looks up the value of the specified [key], or add a new value if it isn't there.
///
/// Returns the deserialized value associated to [key], if there is one. Otherwise calls [ifAbsent] to get a new value,
/// serializes and associates [key] to that value, and then returns the new value.
dynamic putObjectIfAbsent(String key, dynamic Function() ifAbsent) {
if (!containsKey(key)) setObject(key, ifAbsent());
return getObject(key);
}
/// Removes the storage item with the specified [key] and its associated value.
/// Returns the value associated with [key] before it was removed.
@override
String remove(Object key) {
assert(key is String && key.isNotEmpty);
final previousValue = _backend.remove(key);
_onChanges.add(<String, SimpleChange>{
key: SimpleChange(previousValue: previousValue)
});
return previousValue;
}
/// Associates a given [value] to the specified [key].
void set(String key, String value) {
assert(key.isNotEmpty);
final previousValue = this[key];
_backend[key] = value;
_onChanges.add(<String, SimpleChange>{
key: SimpleChange(previousValue: previousValue, currentValue: value)
});
}
/// Serializes and associates a given [value] to the specified [key].
void setObject(String key, value) => set(key, jsonEncode(value));
/// Creates a new storage service.
WebStorage(this._backend, {bool listenToStorageEvents = false}) {
if (listenToStorageEvents) _subscription = dom.window.onStorage.listen((event) {
if (event.key == null || event.storageArea != _backend) return;
_onChanges.add(<String, SimpleChange>{
event.key: SimpleChange(previousValue: event.oldValue, currentValue: event.newValue)
});
});
}
/// The underlying data store.
final dom.Storage _backend;
/// The handler of "changes" events.
final StreamController<Map<String, SimpleChange>> _onChanges = StreamController<Map<String, SimpleChange>>.broadcast();
/// The subscription to the storage events.
StreamSubscription<dom.StorageEvent> _subscription;
/// Value indicating whether there is no key/value pair in this storage.
@override
bool get isEmpty => _backend.isEmpty;
/// Value indicating whether there is at least one key/value pair in this storage.
@override
bool get isNotEmpty => _backend.isNotEmpty;
/// The keys of this storage.
@override
Iterable<String> get keys => _backend.keys;
/// The number of key/value pairs in this storage.
@override
int get length => _backend.length;
/// The stream of "changes" events.
Stream<Map<String, SimpleChange>> get onChanges => _onChanges.stream;
/// Gets the value associated to the specified [key].
@override
String operator [](Object key) => get(key);
/// Associates the [key] with the given [value].
@override
void operator []=(String key, String value) => set(key, value);
/// Removes all pairs from this storage.
@override
void clear() {
final changes = <String, SimpleChange>{};
for (final entry in entries) changes[entry.key] = SimpleChange(previousValue: entry.value);
_backend.clear();
_onChanges.add(changes);
}
/// Gets a value indicating whether this storage contains the given [key].
@override
bool containsKey(Object key) {
assert(key is String && key.isNotEmpty);
return _backend.containsKey(key);
}
/// Cancels the subscription to the storage events.
void destroy() => _subscription?.cancel();
/// Applies the specified function to each key/value pair of this storage.
@override
void forEach(void Function(String key, String value) action) => _backend.forEach(action);
/// Gets the value associated to the specified [key].
/// Returns the given [defaultValue] if the storage item does not exist.
String get(String key, [String defaultValue]) => containsKey(key) ? _backend[key] : defaultValue;
/// Gets the deserialized value associated to the specified [key].
/// Returns the given [defaultValue] if the storage item does not exist.
dynamic getObject(String key, [defaultValue]) {
try {
final value = this[key];
return value is String ? jsonDecode(value) : defaultValue;
}
on FormatException {
return defaultValue;
}
}
/// Looks up the value of the specified [key], or add a new value if it isn't there.
///
/// Returns the deserialized value associated to [key], if there is one. Otherwise calls [ifAbsent] to get a new value,
/// serializes and associates [key] to that value, and then returns the new value.
dynamic putObjectIfAbsent(String key, dynamic Function() ifAbsent) {
if (!containsKey(key)) setObject(key, ifAbsent());
return getObject(key);
}
/// Removes the storage item with the specified [key] and its associated value.
/// Returns the value associated with [key] before it was removed.
@override
String remove(Object key) {
assert(key is String && key.isNotEmpty);
final previousValue = _backend.remove(key);
_onChanges.add(<String, SimpleChange>{
key: SimpleChange(previousValue: previousValue)
});
return previousValue;
}
/// Associates a given [value] to the specified [key].
void set(String key, String value) {
assert(key.isNotEmpty);
final previousValue = this[key];
_backend[key] = value;
_onChanges.add(<String, SimpleChange>{
key: SimpleChange(previousValue: previousValue, currentValue: value)
});
}
/// Serializes and associates a given [value] to the specified [key].
void setObject(String key, value) => set(key, jsonEncode(value));
}

/// Provides access to the local storage.
class LocalStorage extends WebStorage {

/// Creates a new storage service.
LocalStorage({bool listenToStorageEvents = false}):
super(dom.window.localStorage, listenToStorageEvents: listenToStorageEvents);
/// Creates a new storage service.
LocalStorage({bool listenToStorageEvents = false}):
super(dom.window.localStorage, listenToStorageEvents: listenToStorageEvents);
}

/// Provides access to the session storage.
class SessionStorage extends WebStorage {

/// Creates a new storage service.
SessionStorage({bool listenToStorageEvents = false}):
super(dom.window.sessionStorage, listenToStorageEvents: listenToStorageEvents);
/// Creates a new storage service.
SessionStorage({bool listenToStorageEvents = false}):
super(dom.window.sessionStorage, listenToStorageEvents: listenToStorageEvents);
}

+ 8
- 8
lib/webstorage.dart View File

@@ -1,12 +1,12 @@
/// Services for interacting with the [Web Storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage).
library webstorage;

import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:html' as dom;
import 'package:json_annotation/json_annotation.dart';
import "dart:async";
import "dart:collection";
import "dart:convert";
import "dart:html" as dom;
import "package:json_annotation/json_annotation.dart";

part 'webstorage.g.dart';
part 'src/simple_change.dart';
part 'src/storage.dart';
part "webstorage.g.dart";
part "src/simple_change.dart";
part "src/storage.dart";

+ 1
- 1
pubspec.yaml View File

@@ -7,7 +7,7 @@ issue_tracker: https://git.belin.io/cedx/webstorage.dart/issues
repository: https://git.belin.io/cedx/webstorage.dart

environment:
sdk: '>=2.8.0 <3.0.0'
sdk: ">=2.8.0 <3.0.0"

dependencies:
json_annotation: ^3.0.1


+ 29
- 29
test/simple_change_test.dart View File

@@ -1,35 +1,35 @@
import 'package:test/test.dart';
import 'package:webstorage/webstorage.dart';
import "package:test/test.dart";
import "package:webstorage/webstorage.dart";

/// Tests the features of the [SimpleChange] class.
void main() => group('SimpleChange', () {
group('.fromJson()', () {
test('should return an empty instance with an empty map', () {
final change = SimpleChange.fromJson({});
expect(change.currentValue, isNull);
expect(change.previousValue, isNull);
});
void main() => group("SimpleChange", () {
group(".fromJson()", () {
test("should return an empty instance with an empty map", () {
final change = SimpleChange.fromJson({});
expect(change.currentValue, isNull);
expect(change.previousValue, isNull);
});

test('should return an initialized instance with a non-empty map', () {
final change = SimpleChange.fromJson({'currentValue': 'foo', 'previousValue': 'bar'});
expect(change.currentValue, 'foo');
expect(change.previousValue, 'bar');
});
});
test("should return an initialized instance with a non-empty map", () {
final change = SimpleChange.fromJson({"currentValue": "foo", "previousValue": "bar"});
expect(change.currentValue, "foo");
expect(change.previousValue, "bar");
});
});

group('.toJson()', () {
test('should return a map with default values for a newly created instance', () {
final map = const SimpleChange().toJson();
expect(map, hasLength(2));
expect(map['currentValue'], isNull);
expect(map['previousValue'], isNull);
});
group(".toJson()", () {
test("should return a map with default values for a newly created instance", () {
final map = const SimpleChange().toJson();
expect(map, hasLength(2));
expect(map["currentValue"], isNull);
expect(map["previousValue"], isNull);
});

test('should return a non-empty map for an initialized instance', () {
final map = const SimpleChange(currentValue: 'bar', previousValue: 'baz').toJson();
expect(map, hasLength(2));
expect(map['currentValue'], 'bar');
expect(map['previousValue'], 'baz');
});
});
test("should return a non-empty map for an initialized instance", () {
final map = const SimpleChange(currentValue: "bar", previousValue: "baz").toJson();
expect(map, hasLength(2));
expect(map["currentValue"], "bar");
expect(map["previousValue"], "baz");
});
});
});

+ 224
- 224
test/storage_test.dart View File

@@ -1,227 +1,227 @@
import 'dart:html' as dom;
import 'package:test/test.dart';
import 'package:webstorage/webstorage.dart';
import "dart:html" as dom;
import "package:test/test.dart";
import "package:webstorage/webstorage.dart";

/// Tests the features of the [WebStorage] class.
void main() => group('WebStorage', () {
setUp(dom.window.sessionStorage.clear);
group('.keys', () {
test('should return an empty list for an empty storage', () {
expect(SessionStorage().keys, isEmpty);
});
test('should return the list of keys for a non-empty storage', () {
dom.window.sessionStorage['foo'] = 'bar';
dom.window.sessionStorage['bar'] = 'baz';
expect(SessionStorage().keys, orderedEquals(['foo', 'bar']));
});
});
group('.length', () {
test('should return zero for an empty storage', () {
expect(SessionStorage(), isEmpty);
});
test('should return the number of entries for a non-empty storage', () {
dom.window.sessionStorage['foo'] = 'bar';
dom.window.sessionStorage['bar'] = 'baz';
expect(SessionStorage(), hasLength(2));
});
});
group('.onChanges', () {
test('should trigger an event when a value is added', () {
final storage = SessionStorage();
storage.onChanges.listen(expectAsync1((changes) {
expect(changes, hasLength(1));
final record = changes.values.first;
expect(changes.keys.first, 'foo');
expect(record.currentValue, 'bar');
expect(record.previousValue, isNull);
}));
storage['foo'] = 'bar';
});
test('should trigger an event when a value is updated', () {
dom.window.sessionStorage['foo'] = 'bar';
final storage = SessionStorage();
storage.onChanges.listen(expectAsync1((changes) {
expect(changes, hasLength(1));
final record = changes.values.first;
expect(changes.keys.first, 'foo');
expect(record.currentValue, 'baz');
expect(record.previousValue, 'bar');
}));
storage['foo'] = 'baz';
});
test('should trigger an event when a value is removed', () {
dom.window.sessionStorage['foo'] = 'bar';
final storage = SessionStorage();
storage.onChanges.listen(expectAsync1((changes) {
expect(changes, hasLength(1));
final record = changes.values.first;
expect(changes.keys.first, 'foo');
expect(record.currentValue, isNull);
expect(record.previousValue, 'bar');
}));
storage.remove('foo');
});
test('should trigger an event when the storage is cleared', () {
dom.window.sessionStorage['foo'] = 'bar';
dom.window.sessionStorage['bar'] = 'baz';
final storage = SessionStorage();
storage.onChanges.listen(expectAsync1((changes) {
expect(changes, hasLength(2));
var record = changes.values.first;
expect(changes.keys.first, 'foo');
expect(record.currentValue, isNull);
expect(record.previousValue, 'bar');
record = changes.values.last;
expect(changes.keys.last, 'bar');
expect(record.currentValue, isNull);
expect(record.previousValue, 'baz');
}));
storage.clear();
});
});
group('.clear()', () {
test('should remove all storage entries', () {
dom.window.sessionStorage['foo'] = 'bar';
dom.window.sessionStorage['bar'] = 'baz';
final storage = SessionStorage();
expect(storage, hasLength(2));
storage.clear();
expect(storage, isEmpty);
});
});
group('.containsKey()', () {
test('should return `false` if the specified key is not contained', () {
expect(SessionStorage().containsKey('foo'), isFalse);
});
test('should return `true` if the specified key is contained', () {
final storage = SessionStorage();
dom.window.sessionStorage['foo'] = 'bar';
expect(storage.containsKey('foo'), isTrue);
expect(storage.containsKey('bar'), isFalse);
});
});
group('.get()', () {
test('should properly get the storage entries', () {
final storage = SessionStorage();
expect(storage['foo'], isNull);
dom.window.sessionStorage['foo'] = 'bar';
expect(storage['foo'], 'bar');
dom.window.sessionStorage['foo'] = '123';
expect(storage['foo'], '123');
});
test('should return the given default value if the key is not found', () {
expect(SessionStorage().get('bar', '123'), '123');
});
});
group('.getObject()', () {
test('should properly get the deserialized storage entries', () {
final storage = SessionStorage();
expect(storage.getObject('foo'), isNull);
expect(storage.getObject('foo', {'key': 'value'}), isA<Map>()
.having((map) => map, 'length', hasLength(1))
.having((map) => map['key'], 'entry', 'value'));
dom.window.sessionStorage['foo'] = '123';
expect(storage.getObject('foo'), 123);
dom.window.sessionStorage['foo'] = '"bar"';
expect(storage.getObject('foo'), 'bar');
dom.window.sessionStorage['foo'] = '{"key": "value"}';
expect(storage.getObject('foo'), isA<Map>()
.having((map) => map, 'length', hasLength(1))
.having((map) => map['key'], 'entry', 'value'));
});
test('should return the default value if the value can\'t be deserialized', () {
dom.window.sessionStorage['foo'] = 'bar';
expect(SessionStorage().getObject('foo', 'defaultValue'), 'defaultValue');
});
});
group('.putObjectIfAbsent()', () {
test('should add a new entry if it does not exist', () {
final storage = SessionStorage();
expect(dom.window.sessionStorage['foo'], isNull);
expect(storage.putObjectIfAbsent('foo', () => 123), 123);
expect(dom.window.sessionStorage['foo'], '123');
});
test('should not add a new entry if it already exists', () {
final storage = SessionStorage();
dom.window.sessionStorage['bar'] = '123';
expect(storage.putObjectIfAbsent('bar', () => 456), 123);
expect(dom.window.sessionStorage['bar'], '123');
});
});
group('.remove()', () {
test('should properly remove the storage entries', () {
final storage = SessionStorage();
dom.window.sessionStorage['foo'] = 'bar';
dom.window.sessionStorage['bar'] = 'baz';
expect(dom.window.sessionStorage['foo'], 'bar');
storage.remove('foo');
expect(dom.window.sessionStorage['foo'], isNull);
expect(dom.window.sessionStorage['bar'], 'baz');
storage.remove('bar');
expect(dom.window.sessionStorage['bar'], isNull);
});
});
group('.set()', () {
test('should properly set the storage entries', () {
final storage = SessionStorage();
expect(dom.window.sessionStorage['foo'], isNull);
storage['foo'] = 'bar';
expect(dom.window.sessionStorage['foo'], 'bar');
storage['foo'] = '123';
expect(dom.window.sessionStorage['foo'], '123');
});
});
group('.setObject()', () {
test('should properly serialize and set the storage entries', () {
final storage = SessionStorage();
expect(dom.window.sessionStorage['foo'], isNull);
storage.setObject('foo', 123);
expect(dom.window.sessionStorage['foo'], '123');
storage.setObject('foo', 'bar');
expect(dom.window.sessionStorage['foo'], '"bar"');
storage.setObject('foo', {'key': 'value'});
expect(dom.window.sessionStorage['foo'], '{"key":"value"}');
});
});
void main() => group("WebStorage", () {
setUp(dom.window.sessionStorage.clear);
group(".keys", () {
test("should return an empty list for an empty storage", () {
expect(SessionStorage().keys, isEmpty);
});
test("should return the list of keys for a non-empty storage", () {
dom.window.sessionStorage["foo"] = "bar";
dom.window.sessionStorage["bar"] = "baz";
expect(SessionStorage().keys, orderedEquals(["foo", "bar"]));
});
});
group(".length", () {
test("should return zero for an empty storage", () {
expect(SessionStorage(), isEmpty);
});
test("should return the number of entries for a non-empty storage", () {
dom.window.sessionStorage["foo"] = "bar";
dom.window.sessionStorage["bar"] = "baz";
expect(SessionStorage(), hasLength(2));
});
});
group(".onChanges", () {
test("should trigger an event when a value is added", () {
final storage = SessionStorage();
storage.onChanges.listen(expectAsync1((changes) {
expect(changes, hasLength(1));
final record = changes.values.first;
expect(changes.keys.first, "foo");
expect(record.currentValue, "bar");
expect(record.previousValue, isNull);
}));
storage["foo"] = "bar";
});
test("should trigger an event when a value is updated", () {
dom.window.sessionStorage["foo"] = "bar";
final storage = SessionStorage();
storage.onChanges.listen(expectAsync1((changes) {
expect(changes, hasLength(1));
final record = changes.values.first;
expect(changes.keys.first, "foo");
expect(record.currentValue, "baz");
expect(record.previousValue, "bar");
}));
storage["foo"] = "baz";
});
test("should trigger an event when a value is removed", () {
dom.window.sessionStorage["foo"] = "bar";
final storage = SessionStorage();
storage.onChanges.listen(expectAsync1((changes) {
expect(changes, hasLength(1));
final record = changes.values.first;
expect(changes.keys.first, "foo");
expect(record.currentValue, isNull);
expect(record.previousValue, "bar");
}));
storage.remove("foo");
});
test("should trigger an event when the storage is cleared", () {
dom.window.sessionStorage["foo"] = "bar";
dom.window.sessionStorage["bar"] = "baz";
final storage = SessionStorage();
storage.onChanges.listen(expectAsync1((changes) {
expect(changes, hasLength(2));
var record = changes.values.first;
expect(changes.keys.first, "foo");
expect(record.currentValue, isNull);
expect(record.previousValue, "bar");
record = changes.values.last;
expect(changes.keys.last, "bar");
expect(record.currentValue, isNull);
expect(record.previousValue, "baz");
}));
storage.clear();
});
});
group(".clear()", () {
test("should remove all storage entries", () {
dom.window.sessionStorage["foo"] = "bar";
dom.window.sessionStorage["bar"] = "baz";
final storage = SessionStorage();
expect(storage, hasLength(2));
storage.clear();
expect(storage, isEmpty);
});
});
group(".containsKey()", () {
test("should return `false` if the specified key is not contained", () {
expect(SessionStorage().containsKey("foo"), isFalse);
});
test("should return `true` if the specified key is contained", () {
final storage = SessionStorage();
dom.window.sessionStorage["foo"] = "bar";
expect(storage.containsKey("foo"), isTrue);
expect(storage.containsKey("bar"), isFalse);
});
});
group(".get()", () {
test("should properly get the storage entries", () {
final storage = SessionStorage();
expect(storage["foo"], isNull);
dom.window.sessionStorage["foo"] = "bar";
expect(storage["foo"], "bar");
dom.window.sessionStorage["foo"] = "123";
expect(storage["foo"], "123");
});
test("should return the given default value if the key is not found", () {
expect(SessionStorage().get("bar", "123"), "123");
});
});
group(".getObject()", () {
test("should properly get the deserialized storage entries", () {
final storage = SessionStorage();
expect(storage.getObject("foo"), isNull);
expect(storage.getObject("foo", {"key": "value"}), isA<Map>()
.having((map) => map, "length", hasLength(1))
.having((map) => map["key"], "entry", "value"));
dom.window.sessionStorage["foo"] = "123";
expect(storage.getObject("foo"), 123);
dom.window.sessionStorage["foo"] = '"bar"';
expect(storage.getObject("foo"), "bar");
dom.window.sessionStorage["foo"] = '{"key": "value"}';
expect(storage.getObject("foo"), isA<Map>()
.having((map) => map, "length", hasLength(1))
.having((map) => map["key"], "entry", "value"));
});
test("should return the default value if the value can\"t be deserialized", () {
dom.window.sessionStorage["foo"] = "bar";
expect(SessionStorage().getObject("foo", "defaultValue"), "defaultValue");
});
});
group(".putObjectIfAbsent()", () {
test("should add a new entry if it does not exist", () {
final storage = SessionStorage();
expect(dom.window.sessionStorage["foo"], isNull);
expect(storage.putObjectIfAbsent("foo", () => 123), 123);
expect(dom.window.sessionStorage["foo"], "123");
});
test("should not add a new entry if it already exists", () {
final storage = SessionStorage();
dom.window.sessionStorage["bar"] = "123";
expect(storage.putObjectIfAbsent("bar", () => 456), 123);
expect(dom.window.sessionStorage["bar"], "123");
});
});
group(".remove()", () {
test("should properly remove the storage entries", () {
final storage = SessionStorage();
dom.window.sessionStorage["foo"] = "bar";
dom.window.sessionStorage["bar"] = "baz";
expect(dom.window.sessionStorage["foo"], "bar");
storage.remove("foo");
expect(dom.window.sessionStorage["foo"], isNull);
expect(dom.window.sessionStorage["bar"], "baz");
storage.remove("bar");
expect(dom.window.sessionStorage["bar"], isNull);
});
});
group(".set()", () {
test("should properly set the storage entries", () {
final storage = SessionStorage();
expect(dom.window.sessionStorage["foo"], isNull);
storage["foo"] = "bar";
expect(dom.window.sessionStorage["foo"], "bar");
storage["foo"] = "123";
expect(dom.window.sessionStorage["foo"], "123");
});
});
group(".setObject()", () {
test("should properly serialize and set the storage entries", () {
final storage = SessionStorage();
expect(dom.window.sessionStorage["foo"], isNull);
storage.setObject("foo", 123);
expect(dom.window.sessionStorage["foo"], "123");
storage.setObject("foo", "bar");
expect(dom.window.sessionStorage["foo"], '"bar"');
storage.setObject("foo", {"key": "value"});
expect(dom.window.sessionStorage["foo"], '{"key":"value"}');
});
});
});

+ 3
- 3
tool/clean.ps1 View File

@@ -2,10 +2,10 @@
Set-StrictMode -Version Latest
Set-Location (Split-Path $PSScriptRoot)

foreach ($item in '.dart_tool/build', 'build', 'doc/api', 'www') {
if (Test-Path $item) { Remove-Item $item -Recurse }
foreach ($item in ".dart_tool/build", "build", "doc/api", "www") {
if (Test-Path $item) { Remove-Item $item -Recurse }
}

foreach ($item in Get-ChildItem var -Exclude .gitkeep) {
Remove-Item $item -Recurse
Remove-Item $item -Recurse
}

Loading…
Cancel
Save