etcetera 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Etcetera
4
+ module AppStrategy
5
+ # This strategy implements the {XDG Base Directories Specification}[https://specifications.freedesktop.org/basedir/latest/]. It is the most common on Linux, but is increasingly being adopted elsewhere.
6
+ #
7
+ # This initial example removes all the XDG environment variables to show the strategy’s use of the XDG default directories.
8
+ #
9
+ # require "etcetera"
10
+ #
11
+ # # Remove the environment variables that the strategy reads from.
12
+ # ENV.delete("XDG_CONFIG_HOME")
13
+ # ENV.delete("XDG_DATA_HOME")
14
+ # ENV.delete("XDG_CACHE_HOME")
15
+ # ENV.delete("XDG_STATE_HOME")
16
+ # ENV.delete("XDG_RUNTIME_DIR")
17
+ #
18
+ # app_strategy = Etcetera::AppStrategy::Xdg.new(Etcetera::AppStrategy::AppStrategyArgs.new(
19
+ # top_level_domain: "org",
20
+ # author: "Acme Corp",
21
+ # app_name: "Frobnicator Plus"
22
+ # ))
23
+ #
24
+ # home_dir = Etcetera.home_dir
25
+ #
26
+ # app_strategy.home_dir == home_dir # => true
27
+ # app_strategy.config_dir.relative_path_from(home_dir) # => #<Pathname:.config/frobnicator-plus>
28
+ # app_strategy.data_dir.relative_path_from(home_dir) # => #<Pathname:.local/share/frobnicator-plus>
29
+ # app_strategy.cache_dir.relative_path_from(home_dir) # => #<Pathname:.cache/frobnicator-plus>
30
+ # app_strategy.state_dir.relative_path_from(home_dir) # => #<Pathname:.local/state/frobnicator-plus>
31
+ # app_strategy.runtime_dir # => nil
32
+ #
33
+ # This next example gives the environment variables values:
34
+ #
35
+ # require "etcetera"
36
+ #
37
+ # # We need to conditionally set these to ensure that they are absolute paths both on Windows and other systems.
38
+ # windows = Gem.win_platform?
39
+ # config_path = windows ? "C:\\my_config_location\\" : "/my_config_location/"
40
+ # data_path = windows ? "C:\\my_data_location\\" : "/my_data_location/"
41
+ # cache_path = windows ? "C:\\my_cache_location\\" : "/my_cache_location/"
42
+ # state_path = windows ? "C:\\my_state_location\\" : "/my_state_location/"
43
+ # runtime_path = windows ? "C:\\my_runtime_location\\" : "/my_runtime_location/"
44
+ #
45
+ # ENV["XDG_CONFIG_HOME"] = config_path
46
+ # ENV["XDG_DATA_HOME"] = data_path
47
+ # ENV["XDG_CACHE_HOME"] = cache_path
48
+ # ENV["XDG_STATE_HOME"] = state_path
49
+ # ENV["XDG_RUNTIME_DIR"] = runtime_path
50
+ #
51
+ # app_strategy = Etcetera::AppStrategy::Xdg.new(Etcetera::AppStrategy::AppStrategyArgs.new(
52
+ # top_level_domain: "org",
53
+ # author: "Acme Corp",
54
+ # app_name: "Frobnicator Plus"
55
+ # ))
56
+ #
57
+ # app_strategy.config_dir == Pathname.new(config_path).join("frobnicator-plus") # => true
58
+ # app_strategy.data_dir == Pathname.new(data_path).join("frobnicator-plus") # => true
59
+ # app_strategy.cache_dir == Pathname.new(cache_path).join("frobnicator-plus") # => true
60
+ # app_strategy.state_dir == Pathname.new(state_path).join("frobnicator-plus") # => true
61
+ # app_strategy.runtime_dir == Pathname.new(runtime_path).join("frobnicator-plus") # => true
62
+ #
63
+ # The XDG spec requires that when the environment variables’ values are not absolute paths, their values should be ignored. This example exemplifies this behaviour:
64
+ #
65
+ # require "etcetera"
66
+ #
67
+ # # Remove the environment variables that the strategy reads from.
68
+ # ENV["XDG_CONFIG_HOME"] = "relative_path/"
69
+ # ENV["XDG_DATA_HOME"] = "./another_one/"
70
+ # ENV["XDG_CACHE_HOME"] = "yet_another/"
71
+ # ENV["XDG_STATE_HOME"] = "./and_another"
72
+ # ENV["XDG_RUNTIME_DIR"] = "relative_path/"
73
+ #
74
+ # app_strategy = Etcetera::AppStrategy::Xdg.new(Etcetera::AppStrategy::AppStrategyArgs.new(
75
+ # top_level_domain: "org",
76
+ # author: "Acme Corp",
77
+ # app_name: "Frobnicator Plus"
78
+ # ))
79
+ #
80
+ # home_dir = Etcetera.home_dir
81
+ #
82
+ # # We still get the default values.
83
+ # app_strategy.config_dir.relative_path_from(home_dir) # => #<Pathname:.config/frobnicator-plus>
84
+ # app_strategy.data_dir.relative_path_from(home_dir) # => #<Pathname:.local/share/frobnicator-plus>
85
+ # app_strategy.cache_dir.relative_path_from(home_dir) # => #<Pathname:.cache/frobnicator-plus>
86
+ # app_strategy.state_dir.relative_path_from(home_dir) # => #<Pathname:.local/state/frobnicator-plus>
87
+ # app_strategy.runtime_dir # => nil
88
+ class Xdg < Data.define(:base_strategy, :unixy_name)
89
+ include AppStrategy
90
+
91
+ # Create a new \Xdg \AppStrategy
92
+ def self.new(args) = super(base_strategy: BaseStrategy::Xdg.new, unixy_name: args.unixy_name)
93
+
94
+ private :base_strategy, :unixy_name
95
+
96
+ def home_dir = base_strategy.home_dir
97
+
98
+ def config_dir = base_strategy.config_dir.join(unixy_name)
99
+
100
+ def data_dir = base_strategy.data_dir.join(unixy_name)
101
+
102
+ def cache_dir = base_strategy.cache_dir.join(unixy_name)
103
+
104
+ def state_dir = base_strategy.state_dir.join(unixy_name)
105
+
106
+ def runtime_dir = base_strategy.runtime_dir&.join(unixy_name)
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Etcetera
4
+ # These strategies require you to provide some information on your application, and they will in turn locate the configuration/data/cache directory specifically for your application.
5
+ #
6
+ # Allows applications to retrieve the paths of configuration, data, and cache directories specifically for them.
7
+ module AppStrategy
8
+ # The arguments to the creator method of an AppStrategy.
9
+ #
10
+ # +top_level_domain+:: The top level domain of the application, e.g. +com+, +org+, or <tt>io.github</tt>.
11
+ # +author+:: The name of the author of the application.
12
+ # +app_name+:: The application’s name. This should be capitalised if appropriate.
13
+ class AppStrategyArgs < Data.define(:top_level_domain, :author, :app_name)
14
+ def initialize(top_level_domain: "", author: "", app_name: "") = super
15
+
16
+ # Constructs a bunde identifier from an +AppStrategyArgs+.
17
+ #
18
+ # require "etcetera"
19
+ #
20
+ # strategy_args = Etcetera::AppStrategy::AppStrategyArgs.new(
21
+ # top_level_domain: "org",
22
+ # author: "Acme Corp",
23
+ # app_name: "Frobnicator Plus"
24
+ # )
25
+ #
26
+ # strategy_args.bundle_id # => "org.acme-corp.Frobnicator-Plus"
27
+ def bundle_id
28
+ author = self.author.downcase.tr(" ", "-")
29
+ app_name = self.app_name.tr(" ", "-")
30
+ [top_level_domain, author, app_name].reject(&:empty?).join(".")
31
+ end
32
+
33
+ # Returns a ‘unixy’ version of the application’s name, akin to what would usually be used as a binary name.
34
+ #
35
+ # require "etcetera"
36
+ #
37
+ # strategy_args = Etcetera::AppStrategy::AppStrategyArgs.new(
38
+ # top_level_domain: "org",
39
+ # author: "Acme Corp",
40
+ # app_name: "Frobnicator Plus"
41
+ # )
42
+ #
43
+ # strategy_args.unixy_name # => "frobnicator-plus"
44
+ def unixy_name = app_name.downcase.tr(" ", "-")
45
+ end
46
+
47
+ ##
48
+ # :method: home_dir
49
+ # :call-seq:
50
+ # home_dir -> Pathname
51
+ #
52
+ # Gets the home directory of the current user.
53
+
54
+ # @!method home_dir
55
+ # Gets the home directory of the current user.
56
+ # @return [Pathname]
57
+
58
+ ##
59
+ # :method: config_dir
60
+ # :call-seq:
61
+ # config_dir -> Pathname
62
+ #
63
+ # Gets the configuration directory for your application.
64
+
65
+ # @!method config_dir
66
+ # Gets the configuration directory for your application.
67
+ # @return [Pathname]
68
+
69
+ ##
70
+ # :method: data_dir
71
+ # :call-seq:
72
+ # data_dir -> Pathname
73
+ #
74
+ # Gets the data directory for your application.
75
+
76
+ # @!method data_dir
77
+ # Gets the data directory for your application.
78
+ # @return [Pathname]
79
+
80
+ ##
81
+ # :method: cache_dir
82
+ # :call-seq:
83
+ # cache_dir -> Pathname
84
+ #
85
+ # Gets the cache directory for your application.
86
+
87
+ # @!method cache_dir
88
+ # Gets the cache directory for your application.
89
+ # @return [Pathname]
90
+
91
+ ##
92
+ # :method: state_dir
93
+ # :call-seq:
94
+ # state_dir -> Pathname or nil
95
+ #
96
+ # Gets the state directory for your application.
97
+ # Currently, only the Xdg & Unix strategies support
98
+ # this.
99
+
100
+ # @!method state_dir
101
+ # Gets the state directory for your application.
102
+ # Currently, only the {Etcetera::AppStrategy::Xdg Xdg} & {Etcetera::AppStrategy::Unix Unix} strategies support
103
+ # this.
104
+ # @return [Pathname, nil]
105
+
106
+ ##
107
+ # :method: runtime_dir
108
+ # :call-seq:
109
+ # runtime_dir -> Pathname or nil
110
+ #
111
+ # Gets the runtime directory for your application.
112
+ # Currently, only the Xdg & Unix strategies support
113
+ # this.
114
+ #
115
+ # Note: The {XDG Base Directory Specification}[https://specifications.freedesktop.org/basedir/latest/] places additional requirements on this
116
+ # directory related to ownership, permissions, and persistence. This library does not check
117
+ # these requirements.
118
+
119
+ # @!method runtime_dir
120
+ # Gets the runtime directory for your application.
121
+ # Currently, only the {Etcetera::AppStrategy::Xdg Xdg} & {Etcetera::AppStrategy::Unix Unix} strategies support
122
+ # this.
123
+ #
124
+ # Note: The {XDG Base Directory Specification}[https://specifications.freedesktop.org/basedir/latest/] places additional requirements on this
125
+ # directory related to ownership, permissions, and persistence. This library does not check
126
+ # these requirements.
127
+ # @return [Pathname, nil]
128
+
129
+ # Constructs a path inside your application’s configuration directory to which a path of your choice has been appended.
130
+ def in_config_dir(path) = config_dir.join(path)
131
+
132
+ # Constructs a path inside your application’s data directory to which a path of your choice has been appended.
133
+ def in_data_dir(path) = data_dir.join(path)
134
+
135
+ # Constructs a path inside your application’s cache directory to which a path of your choice has been appended.
136
+ def in_cache_dir(path) = cache_dir.join(path)
137
+
138
+ # Constructs a path inside your application’s state directory to which a path of your choice has been appended.
139
+ #
140
+ # Currently, this is only implemented for the Xdg strategy.
141
+ def in_state_dir(path) = state_dir&.join(path)
142
+
143
+ # Constructs a path inside your application’s runtime directory to which a path of your choice has been appended.
144
+ # Currently, only the Xdg & Unix strategies support
145
+ # this.
146
+ #
147
+ # See the note in #runtime_dir for more information.
148
+ def in_runtime_dir(path) = runtime_dir&.join(path)
149
+
150
+ # Returns the current OS’s native AppStrategy.
151
+ # This uses the Windows strategy on \Windows, Apple on macOS & iOS, and Xdg everywhere else.
152
+ # This is the convention used by most GUI applications.
153
+ def self.choose_native_strategy(args)
154
+ if Gem.win_platform? then Windows.new(args)
155
+ elsif RUBY_PLATFORM.include?("darwin") then Apple.new(args)
156
+ else Xdg.new(args)
157
+ end
158
+ end
159
+
160
+ # Returns the current OS’s default AppStrategy.
161
+ # This uses the Windows strategy on \Windows, and Xdg everywhere else.
162
+ # This is the convention used by most CLI applications.
163
+ def self.choose_app_strategy(args)
164
+ Gem.win_platform? ? Windows.new(args) : Xdg.new(args)
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Etcetera
4
+ module BaseStrategy
5
+ # This is the strategy created by \Apple for use on macOS and iOS devices. It is always used by GUI apps on macOS, and is sometimes used by command-line applications there too. iOS only has GUIs, so all iOS applications follow this strategy. The specification is available {here}[https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW1].
6
+ #
7
+ # require "etcetera"
8
+ #
9
+ # base_strategy = Etcetera::BaseStrategy::Apple.new
10
+ #
11
+ # home_dir = Etcetera.home_dir
12
+ #
13
+ # base_strategy.home_dir == home_dir # => true
14
+ # base_strategy.config_dir.relative_path_from(home_dir) # => #<Pathname:Library/Preferences>
15
+ # base_strategy.data_dir.relative_path_from(home_dir) # => #<Pathname:Library/Application Support>
16
+ # base_strategy.cache_dir.relative_path_from(home_dir) # => #<Pathname:Library/Caches>
17
+ # base_strategy.state_dir # => nil
18
+ # base_strategy.runtime_dir # => nil
19
+ class Apple < Data.define(:home_dir)
20
+ # Create a new \Apple \BaseStrategy
21
+ def initialize(home_dir: Etcetera.home_dir) = super
22
+
23
+ def config_dir = home_dir.join("Library/Preferences/")
24
+
25
+ def data_dir = home_dir.join("Library/Application Support/")
26
+
27
+ def cache_dir = home_dir.join("Library/Caches/")
28
+
29
+ def state_dir = nil
30
+
31
+ def runtime_dir = nil
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Etcetera
4
+ module BaseStrategy # rubocop:disable Style/Documentation -- documented in base_strategy.rb
5
+ if Gem.win_platform?
6
+ require_relative "../windows_sys"
7
+
8
+ # The C runtime function the crate declares in an `extern "C"` block.
9
+ module CRT # :nodoc: all
10
+ extend Fiddle::Importer
11
+
12
+ dlload "ucrtbase.dll"
13
+ extern "size_t wcslen(void* buf)"
14
+ end
15
+ private_constant :CRT
16
+ end
17
+
18
+ # This strategy follows Windows’ conventions. It seems that all \Windows GUI apps, and some command-line ones follow this pattern. The specification is available {here}[https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid].
19
+ #
20
+ # This initial example removes all the relevant environment variables to show the strategy’s use of the:
21
+ # - (on \Windows) SHGetKnownFolderPath API.
22
+ # - (on non-Windows) \Windows default directories.
23
+ #
24
+ # require "etcetera"
25
+ #
26
+ # # Remove the environment variables that the strategy reads from.
27
+ # ENV.delete("HOME")
28
+ # ENV.delete("APPDATA")
29
+ # ENV.delete("LOCALAPPDATA")
30
+ #
31
+ # base_strategy = Etcetera::BaseStrategy::Windows.new
32
+ #
33
+ # home_dir = Etcetera.home_dir
34
+ #
35
+ # base_strategy.home_dir == home_dir # => true
36
+ # base_strategy.config_dir.relative_path_from(home_dir) # => #<Pathname:AppData/Roaming>
37
+ # base_strategy.data_dir.relative_path_from(home_dir) # => #<Pathname:AppData/Roaming>
38
+ # base_strategy.cache_dir.relative_path_from(home_dir) # => #<Pathname:AppData/Local>
39
+ # base_strategy.state_dir # => nil
40
+ # base_strategy.runtime_dir # => nil
41
+ #
42
+ # This next example gives the environment variables values:
43
+ #
44
+ # require "etcetera"
45
+ #
46
+ # windows = Gem.win_platform?
47
+ # home_path = windows ? "C:/foo/" : Etcetera.home_dir.to_s
48
+ # data_path = windows ? "C:\\bar\\" : "/bar/"
49
+ # cache_path = windows ? "C:\\baz\\" : "/baz/"
50
+ #
51
+ # ENV["HOME"] = home_path
52
+ # ENV["APPDATA"] = data_path
53
+ # ENV["LOCALAPPDATA"] = cache_path
54
+ #
55
+ # base_strategy = Etcetera::BaseStrategy::Windows.new
56
+ #
57
+ # base_strategy.home_dir == Pathname.new(home_path) # => true
58
+ # base_strategy.config_dir == Pathname.new(data_path) # => true
59
+ # base_strategy.data_dir == Pathname.new(data_path) # => true
60
+ # base_strategy.cache_dir == Pathname.new(cache_path) # => true
61
+ # base_strategy.state_dir # => nil
62
+ # base_strategy.runtime_dir # => nil
63
+ class Windows < Data.define(:home_dir)
64
+ # Create a new \Windows \BaseStrategy
65
+ def initialize(home_dir: Etcetera.home_dir) = super
66
+
67
+ def config_dir = data_dir
68
+
69
+ def data_dir = dir_inner("APPDATA") || home_dir.join("AppData").join("Roaming")
70
+
71
+ def cache_dir = dir_inner("LOCALAPPDATA") || home_dir.join("AppData").join("Local")
72
+
73
+ def state_dir = nil
74
+
75
+ def runtime_dir = nil
76
+
77
+ private
78
+
79
+ def dir_inner(env)
80
+ value = ENV.fetch(env, "")
81
+ value.empty? ? dir_crt(env) : Pathname.new(value)
82
+ end
83
+
84
+ if Gem.win_platform?
85
+ # Ref: https://github.com/rust-lang/cargo/blob/home-0.5.11/crates/home/src/windows.rs
86
+ # We should keep this code in sync with the above.
87
+ def dir_crt(env)
88
+ folder_id = case env
89
+ when "APPDATA" then WindowsSys::FOLDERID_RoamingAppData
90
+ when "LOCALAPPDATA" then WindowsSys::FOLDERID_LocalAppData
91
+ else return nil
92
+ end
93
+
94
+ path = Fiddle::Pointer.new(0)
95
+ case WindowsSys.SHGetKnownFolderPath(folder_id.to_ptr, WindowsSys::KF_FLAG_DONT_VERIFY, nil, path.ref)
96
+ when WindowsSys::S_OK
97
+ path_slice = path[0, CRT.wcslen(path) * 2]
98
+ s = path_slice.force_encoding(Encoding::UTF_16LE).encode(Encoding::UTF_8, invalid: :replace)
99
+ WindowsSys.CoTaskMemFree(path)
100
+ Pathname.new(s)
101
+ else
102
+ # Free any allocated memory even on failure. A null ptr is a no-op for `CoTaskMemFree`.
103
+ WindowsSys.CoTaskMemFree(path)
104
+ nil
105
+ end
106
+ end
107
+ else
108
+ def dir_crt(_env) = nil
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Etcetera
4
+ module BaseStrategy
5
+ # This strategy implements the {XDG Base Directories Specification}[https://specifications.freedesktop.org/basedir/latest/]. It is the most common on Linux, but is increasingly being adopted elsewhere.
6
+ #
7
+ # This initial example removes all the XDG environment variables to show the strategy’s use of the XDG default directories.
8
+ #
9
+ # require "etcetera"
10
+ #
11
+ # # Remove the environment variables that the strategy reads from.
12
+ # ENV.delete("XDG_CONFIG_HOME")
13
+ # ENV.delete("XDG_DATA_HOME")
14
+ # ENV.delete("XDG_CACHE_HOME")
15
+ # ENV.delete("XDG_STATE_HOME")
16
+ # ENV.delete("XDG_RUNTIME_DIR")
17
+ #
18
+ # base_strategy = Etcetera::BaseStrategy::Xdg.new
19
+ #
20
+ # home_dir = Etcetera.home_dir
21
+ #
22
+ # base_strategy.home_dir == home_dir # => true
23
+ # base_strategy.config_dir.relative_path_from(home_dir) # => #<Pathname:.config>
24
+ # base_strategy.data_dir.relative_path_from(home_dir) # => #<Pathname:.local/share>
25
+ # base_strategy.cache_dir.relative_path_from(home_dir) # => #<Pathname:.cache>
26
+ # base_strategy.state_dir.relative_path_from(home_dir) # => #<Pathname:.local/state>
27
+ # base_strategy.runtime_dir # => nil
28
+ #
29
+ # This next example gives the environment variables values:
30
+ #
31
+ # require "etcetera"
32
+ #
33
+ # # We need to conditionally set these to ensure that they are absolute paths both on Windows and other systems.
34
+ # windows = Gem.win_platform?
35
+ # config_path = windows ? "C:\\foo\\" : "/foo/"
36
+ # data_path = windows ? "C:\\bar\\" : "/bar/"
37
+ # cache_path = windows ? "C:\\baz\\" : "/baz/"
38
+ # state_path = windows ? "C:\\foobar\\" : "/foobar/"
39
+ # runtime_path = windows ? "C:\\qux\\" : "/qux/"
40
+ #
41
+ # ENV["XDG_CONFIG_HOME"] = config_path
42
+ # ENV["XDG_DATA_HOME"] = data_path
43
+ # ENV["XDG_CACHE_HOME"] = cache_path
44
+ # ENV["XDG_STATE_HOME"] = state_path
45
+ # ENV["XDG_RUNTIME_DIR"] = runtime_path
46
+ #
47
+ # base_strategy = Etcetera::BaseStrategy::Xdg.new
48
+ #
49
+ # base_strategy.config_dir == Pathname.new(config_path) # => true
50
+ # base_strategy.data_dir == Pathname.new(data_path) # => true
51
+ # base_strategy.cache_dir == Pathname.new(cache_path) # => true
52
+ # base_strategy.state_dir == Pathname.new(state_path) # => true
53
+ # base_strategy.runtime_dir == Pathname.new(runtime_path) # => true
54
+ #
55
+ # The XDG spec requires that when the environment variables’ values are not absolute paths, their values should be ignored. This example exemplifies this behaviour:
56
+ #
57
+ # require "etcetera"
58
+ #
59
+ # # Remove the environment variables that the strategy reads from.
60
+ # ENV["XDG_CONFIG_HOME"] = "foo/"
61
+ # ENV["XDG_DATA_HOME"] = "bar/"
62
+ # ENV["XDG_CACHE_HOME"] = "baz/"
63
+ # ENV["XDG_STATE_HOME"] = "foobar/"
64
+ # ENV["XDG_RUNTIME_DIR"] = "qux/"
65
+ #
66
+ # base_strategy = Etcetera::BaseStrategy::Xdg.new
67
+ #
68
+ # home_dir = Etcetera.home_dir
69
+ #
70
+ # # We still get the default values.
71
+ # base_strategy.config_dir.relative_path_from(home_dir) # => #<Pathname:.config>
72
+ # base_strategy.data_dir.relative_path_from(home_dir) # => #<Pathname:.local/share>
73
+ # base_strategy.cache_dir.relative_path_from(home_dir) # => #<Pathname:.cache>
74
+ # base_strategy.state_dir.relative_path_from(home_dir) # => #<Pathname:.local/state>
75
+ # base_strategy.runtime_dir # => nil
76
+ class Xdg < Data.define(:home_dir)
77
+ # Create a new \Xdg \BaseStrategy
78
+ def initialize(home_dir: Etcetera.home_dir) = super
79
+
80
+ def config_dir = env_var_or_default("XDG_CONFIG_HOME", ".config/")
81
+
82
+ def data_dir = env_var_or_default("XDG_DATA_HOME", ".local/share/")
83
+
84
+ def cache_dir = env_var_or_default("XDG_CACHE_HOME", ".cache/")
85
+
86
+ def state_dir = env_var_or_default("XDG_STATE_HOME", ".local/state/")
87
+
88
+ def runtime_dir = env_var_or_none("XDG_RUNTIME_DIR")
89
+
90
+ private
91
+
92
+ def env_var_or_none(env_var)
93
+ path = ENV[env_var]&.then { Pathname.new(it) }
94
+
95
+ # Return nil if the path obtained from the environment variable isn’t absolute.
96
+ path if path&.absolute?
97
+ end
98
+
99
+ def env_var_or_default(env_var, default) = env_var_or_none(env_var) || home_dir.join(default)
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Etcetera
4
+ # These strategies simply provide the user’s configuration, data, and cache directories, without knowing about the application specifically.
5
+ #
6
+ # Provides configuration, data, and cache directories of the current user.
7
+ module BaseStrategy
8
+ ##
9
+ # :method: home_dir
10
+ # :call-seq:
11
+ # home_dir -> Pathname
12
+ #
13
+ # Gets the home directory of the current user.
14
+
15
+ # @!method home_dir
16
+ # Gets the home directory of the current user.
17
+ # @return [Pathname]
18
+
19
+ ##
20
+ # :method: config_dir
21
+ # :call-seq:
22
+ # config_dir -> Pathname
23
+ #
24
+ # Gets the user’s configuration directory.
25
+
26
+ # @!method config_dir
27
+ # Gets the user’s configuration directory.
28
+ # @return [Pathname]
29
+
30
+ ##
31
+ # :method: data_dir
32
+ # :call-seq:
33
+ # data_dir -> Pathname
34
+ #
35
+ # Gets the user’s data directory.
36
+
37
+ # @!method data_dir
38
+ # Gets the user’s data directory.
39
+ # @return [Pathname]
40
+
41
+ ##
42
+ # :method: cache_dir
43
+ # :call-seq:
44
+ # cache_dir -> Pathname
45
+ #
46
+ # Gets the user’s cache directory.
47
+
48
+ # @!method cache_dir
49
+ # Gets the user’s cache directory.
50
+ # @return [Pathname]
51
+
52
+ ##
53
+ # :method: state_dir
54
+ # :call-seq:
55
+ # state_dir -> Pathname or nil
56
+ #
57
+ # Gets the user’s state directory.
58
+ # Currently, only the Xdg strategy supports this.
59
+
60
+ # @!method state_dir
61
+ # Gets the user’s state directory.
62
+ # Currently, only the {Etcetera::BaseStrategy::Xdg Xdg} strategy supports this.
63
+ # @return [Pathname, nil]
64
+
65
+ ##
66
+ # :method: runtime_dir
67
+ # :call-seq:
68
+ # runtime_dir -> Pathname or nil
69
+ #
70
+ # Gets the user’s runtime directory.
71
+ # Currently, only the Xdg strategy supports this.
72
+ #
73
+ # Note: The {XDG Base Directory Specification}[https://specifications.freedesktop.org/basedir/latest/] places additional requirements on this
74
+ # directory related to ownership, permissions, and persistence. This library does not check
75
+ # these requirements.
76
+
77
+ # @!method runtime_dir
78
+ # Gets the user’s runtime directory.
79
+ # Currently, only the {Etcetera::BaseStrategy::Xdg Xdg} strategy supports this.
80
+ #
81
+ # Note: The {XDG Base Directory Specification}[https://specifications.freedesktop.org/basedir/latest/] places additional requirements on this
82
+ # directory related to ownership, permissions, and persistence. This library does not check
83
+ # these requirements.
84
+ # @return [Pathname, nil]
85
+
86
+ # Returns the current OS’s native BaseStrategy.
87
+ # This uses the Windows strategy on \Windows, Apple on macOS & iOS, and Xdg everywhere else.
88
+ # This is the convention used by most GUI applications.
89
+ def self.choose_native_strategy
90
+ if Gem.win_platform? then Windows.new
91
+ elsif RUBY_PLATFORM.include?("darwin") then Apple.new
92
+ else Xdg.new
93
+ end
94
+ end
95
+
96
+ # Returns the current OS’s default BaseStrategy.
97
+ # This uses the Windows strategy on \Windows, and Xdg everywhere else.
98
+ # This is the convention used by most CLI applications.
99
+ def self.choose_base_strategy
100
+ Gem.win_platform? ? Windows.new : Xdg.new
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Etcetera
4
+ # Version of the gem, mirroring the version of the Rust crate it ports.
5
+ VERSION = "0.12.0"
6
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fiddle/import"
4
+
5
+ module Etcetera
6
+ # The parts of the windows-sys crate that the crate uses, bound with Fiddle.
7
+ # Only Windows loads this, as only Windows builds of the crate depend on it.
8
+ #
9
+ # Derived from windows-sys 0.61.2 (https://github.com/microsoft/windows-rs),
10
+ # Copyright (c) Microsoft Corporation, used under the MIT License; see
11
+ # LICENSE-WINDOWS-SYS.
12
+ module WindowsSys # :nodoc: all
13
+ # :stopdoc:
14
+
15
+ extend Fiddle::Importer
16
+
17
+ dlload "shell32.dll", "combase.dll"
18
+
19
+ GUID = struct(["uint32_t data1", "uint16_t data2", "uint16_t data3", "uint8_t data4[8]"])
20
+
21
+ def GUID.from_u128(uuid)
22
+ malloc(Fiddle::RUBY_FREE).tap do |guid|
23
+ guid.data1 = uuid >> 96
24
+ guid.data2 = uuid >> 80 & 0xffff
25
+ guid.data3 = uuid >> 64 & 0xffff
26
+ guid.data4 = [uuid & 0xffff_ffff_ffff_ffff].pack("Q>").bytes
27
+ end
28
+ end
29
+
30
+ S_OK = 0
31
+ KF_FLAG_DONT_VERIFY = 16_384
32
+ FOLDERID_LocalAppData = GUID.from_u128(0xf1b32785_6fba_4fcf_9d55_7b8e7f157091)
33
+ FOLDERID_RoamingAppData = GUID.from_u128(0x3eb685db_65f9_4cf6_a03a_e3ef65729f3d)
34
+
35
+ extern "int32_t SHGetKnownFolderPath(void* rfid, uint32_t dwflags, void* htoken, void* ppszpath)"
36
+ extern "void CoTaskMemFree(void* pv)"
37
+ end
38
+ private_constant :WindowsSys
39
+ end