2
2
// for details. All rights reserved. Use of this source code is governed by a
3
3
// BSD-style license that can be found in the LICENSE file.
4
4
5
- /// Utilities to return the Dart SDK location.
5
+ /// Utilities for CLI programs written in dart.
6
+ ///
7
+ /// This library contains information for returning the location of the dart
8
+ /// SDK, and other directories that command-line applications may need to
9
+ /// access. This library aims follows best practices for each platform, honoring
10
+ /// the [XDG Base Directory Specification][1] on Linux and
11
+ /// [File System Basics][2] on Mac OS.
12
+ ///
13
+ /// Many functions require a `productName` , as data should be stored in a
14
+ /// directory unique to your application, as to not avoid clashes with other
15
+ /// programs on the same machine. For example, if you are writing a command-line
16
+ /// application named 'zinger' then `productName` on Linux could be `zinger` . On
17
+ /// MacOS, this should be your bundle identifier (for example,
18
+ /// `com.example.Zinger` ).
19
+ ///
20
+ /// [1] : https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
21
+ /// [2] : https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW1
6
22
library cli_util;
7
23
8
24
import 'dart:async' ;
@@ -13,14 +29,34 @@ import 'package:path/path.dart' as path;
13
29
/// Return the path to the current Dart SDK.
14
30
String getSdkPath () => path.dirname (path.dirname (Platform .resolvedExecutable));
15
31
32
+ // executable are alo mentioned in the XDG spec, but these do not have as well
33
+ // defined of locations on Windows, MacOS.
34
+ enum _BaseDirectory { cache, config, data, runtime, state }
35
+
36
+ /// Get the user-specific application cache folder for the current platform.
37
+ ///
38
+ /// This is a location appropriate for storing non-essential files that may be
39
+ /// removed at any point. This method won't create the directory; It will merely
40
+ /// return the recommended location.
41
+ ///
42
+ /// The folder location depends on the platform:
43
+ /// * `%LOCALAPPDATA%\<productName>` on **Windows**,
44
+ /// * `$HOME/Library/Caches/<productName>` on **Mac OS**,
45
+ /// * `$XDG_CACHE_HOME/<productName>` on **Linux**
46
+ /// (if `$XDG_CACHE_HOME` is defined), and,
47
+ /// * `$HOME/.cache/` otherwise.
48
+ ///
49
+ /// Throws an [EnvironmentNotFoundException] if necessary environment variables
50
+ /// are undefined.
51
+ String applicationCacheHome (String productName) =>
52
+ path.join (_baseDirectory (_BaseDirectory .cache), productName);
53
+
16
54
/// Get the user-specific application configuration folder for the current
17
55
/// platform.
18
56
///
19
57
/// This is a location appropriate for storing application specific
20
- /// configuration for the current user. The [productName] should be unique to
21
- /// avoid clashes with other applications on the same machine. This method won't
22
- /// actually create the folder, merely return the recommended location for
23
- /// storing user-specific application configuration.
58
+ /// configuration for the current user. This method won't create the directory;
59
+ /// It will merely return the recommended location.
24
60
///
25
61
/// The folder location depends on the platform:
26
62
/// * `%APPDATA%\<productName>` on **Windows**,
@@ -29,54 +65,143 @@ String getSdkPath() => path.dirname(path.dirname(Platform.resolvedExecutable));
29
65
/// (if `$XDG_CONFIG_HOME` is defined), and,
30
66
/// * `$HOME/.config/<productName>` otherwise.
31
67
///
32
- /// This aims follows best practices for each platform, honoring the
33
- /// [XDG Base Directory Specification][1] on Linux and [File System Basics][2]
34
- /// on Mac OS.
68
+ /// Throws an [EnvironmentNotFoundException] if necessary environment variables
69
+ /// are undefined.
70
+ String applicationConfigHome (String productName) =>
71
+ path.join (_baseDirectory (_BaseDirectory .config), productName);
72
+
73
+ /// Get the user-specific application data folder for the current platform.
35
74
///
36
- /// Throws an [EnvironmentNotFoundException] if `%APPDATA%` or `$HOME` is needed
37
- /// but undefined.
75
+ /// This is a location appropriate for storing application specific
76
+ /// semi-permanent data for the current user. This method won't create the
77
+ /// directory; It will merely return the recommended location.
38
78
///
39
- /// [1] : https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
40
- /// [2] : https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW1
41
- String applicationConfigHome (String productName) =>
42
- path.join (_configHome, productName);
79
+ /// The folder location depends on the platform:
80
+ /// * `%APPDATA%\<productName>` on **Windows**,
81
+ /// * `$HOME/Library/Application Support/<productName>` on **Mac OS**,
82
+ /// * `$XDG_DATA_HOME/<productName>` on **Linux**
83
+ /// (if `$XDG_DATA_HOME` is defined), and,
84
+ /// * `$HOME/.local/share/<productName>` otherwise.
85
+ ///
86
+ /// Throws an [EnvironmentNotFoundException] if necessary environment variables
87
+ /// are undefined.
88
+ String applicationDataHome (String productName) =>
89
+ path.join (_baseDirectory (_BaseDirectory .data), productName);
90
+
91
+ /// Get the runtime data folder for the current platform.
92
+ ///
93
+ /// This is a location appropriate for storing runtime data for the current
94
+ /// session. This method won't create the directory; It will merely return the
95
+ /// recommended location.
96
+ ///
97
+ /// The folder location depends on the platform:
98
+ /// * `%LOCALAPPDATA%\<productName>` on **Windows**,
99
+ /// * `$HOME/Library/Application Support/<productName>` on **Mac OS**,
100
+ /// * `$XDG_DATA_HOME/<productName>` on **Linux**
101
+ /// (if `$XDG_DATA_HOME` is defined), and,
102
+ /// * `$HOME/.local/share/<productName>` otherwise.
103
+ ///
104
+ /// Throws an [EnvironmentNotFoundException] if necessary environment variables
105
+ /// are undefined.
106
+ String applicationRuntimeDir (String productName) =>
107
+ path.join (_baseDirectory (_BaseDirectory .runtime), productName);
43
108
44
- String get _configHome {
109
+ /// Get the user-specific application state folder for the current platform.
110
+ ///
111
+ /// This is a location appropriate for storing application specific state
112
+ /// for the current user. This differs from [applicationDataHome] insomuch as it
113
+ /// should contain data which should persist restarts, but is not important
114
+ /// enough to be backed up. This method won't create the directory;
115
+ // It will merely return the recommended location.
116
+ ///
117
+ /// The folder location depends on the platform:
118
+ /// * `%APPDATA%\<productName>` on **Windows**,
119
+ /// * `$HOME/Library/Application Support/<productName>` on **Mac OS**,
120
+ /// * `$XDG_DATA_HOME/<productName>` on **Linux**
121
+ /// (if `$XDG_DATA_HOME` is defined), and,
122
+ /// * `$HOME/.local/share/<productName>` otherwise.
123
+ ///
124
+ /// Throws an [EnvironmentNotFoundException] if necessary environment variables
125
+ /// are undefined.
126
+ String applicationStateHome (String productName) =>
127
+ path.join (_baseDirectory (_BaseDirectory .state), productName);
128
+
129
+ String _baseDirectory (_BaseDirectory dir) {
45
130
if (Platform .isWindows) {
46
- final appdata = _env['APPDATA' ];
47
- if (appdata == null ) {
48
- throw EnvironmentNotFoundException (
49
- 'Environment variable %APPDATA% is not defined!' );
131
+ switch (dir) {
132
+ case _BaseDirectory .config:
133
+ case _BaseDirectory .data:
134
+ return _fetchEnvRequired ('APPDATA' );
135
+ case _BaseDirectory .cache:
136
+ case _BaseDirectory .runtime:
137
+ case _BaseDirectory .state:
138
+ return _fetchEnvRequired ('LOCALAPPDATA' );
50
139
}
51
- return appdata;
52
140
}
53
141
54
142
if (Platform .isMacOS) {
55
- return path.join (_home, 'Library' , 'Application Support' );
143
+ switch (dir) {
144
+ case _BaseDirectory .config:
145
+ case _BaseDirectory .data:
146
+ case _BaseDirectory .state:
147
+ return path.join (_home, 'Library' , 'Application Support' );
148
+ case _BaseDirectory .cache:
149
+ return path.join (_home, 'Library' , 'Caches' );
150
+ case _BaseDirectory .runtime:
151
+ // https://stackoverflow.com/a/76799489
152
+ return path.join (_home, 'Library' , 'Caches' , 'TemporaryItems' );
153
+ }
56
154
}
57
155
58
156
if (Platform .isLinux) {
59
- final xdgConfigHome = _env['XDG_CONFIG_HOME' ];
60
- if (xdgConfigHome != null ) {
61
- return xdgConfigHome;
157
+ String xdgEnv;
158
+ switch (dir) {
159
+ case _BaseDirectory .config:
160
+ xdgEnv = 'XDG_CONFIG_HOME' ;
161
+ break ;
162
+ case _BaseDirectory .data:
163
+ xdgEnv = 'XDG_DATA_HOME' ;
164
+ break ;
165
+ case _BaseDirectory .state:
166
+ xdgEnv = 'XDG_STATE_HOME' ;
167
+ break ;
168
+ case _BaseDirectory .cache:
169
+ xdgEnv = 'XDG_CACHE_HOME' ;
170
+ break ;
171
+ case _BaseDirectory .runtime:
172
+ xdgEnv = 'XDG_RUNTIME_HOME' ;
173
+ break ;
174
+ }
175
+ final val = _env[xdgEnv];
176
+ if (val != null ) {
177
+ return val;
62
178
}
63
- // XDG Base Directory Specification says to use $HOME/.config/ when
64
- // $XDG_CONFIG_HOME isn't defined.
65
- return path.join (_home, '.config' );
66
179
}
67
180
68
181
// We have no guidelines, perhaps we should just do: $HOME/.config/
69
182
// same as XDG specification would specify as fallback.
70
- return path.join (_home, '.config' );
183
+ switch (dir) {
184
+ case _BaseDirectory .runtime:
185
+ case _BaseDirectory .cache:
186
+ return path.join (_home, '.cache' );
187
+ case _BaseDirectory .config:
188
+ return path.join (_home, '.config' );
189
+ case _BaseDirectory .data:
190
+ return path.join (_home, '.local' , 'share' );
191
+ case _BaseDirectory .state:
192
+ return path.join (_home, '.local' , 'state' );
193
+ }
71
194
}
72
195
73
- String get _home {
74
- final home = _env['HOME' ];
75
- if (home == null ) {
196
+ String get _home => _fetchEnvRequired ('HOME' );
197
+
198
+ String _fetchEnvRequired (String name) {
199
+ final v = _env[name];
200
+ if (v == null ) {
76
201
throw EnvironmentNotFoundException (
77
- r 'Environment variable $HOME is not defined!' );
202
+ 'Environment variable \$ $ name is not defined!' );
78
203
}
79
- return home ;
204
+ return v ;
80
205
}
81
206
82
207
class EnvironmentNotFoundException implements Exception {
0 commit comments