xccache 0.0.1a1 → 0.0.1a3

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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/lib/xccache/assets/templates/cachemap.html.template +55 -0
  3. data/lib/xccache/assets/templates/cachemap.js.template +95 -0
  4. data/lib/xccache/assets/templates/cachemap.style.css.template +94 -0
  5. data/lib/xccache/assets/templates/framework.info.plist.template +25 -0
  6. data/lib/xccache/assets/templates/framework.modulemap.template +6 -0
  7. data/lib/xccache/assets/templates/resource_bundle_accessor.m.template +27 -0
  8. data/lib/xccache/assets/templates/resource_bundle_accessor.swift.template +24 -0
  9. data/lib/xccache/assets/templates/umbrella.Package.swift.template +183 -0
  10. data/lib/xccache/cache/cachemap.rb +56 -0
  11. data/lib/xccache/command/base.rb +29 -0
  12. data/lib/xccache/command/build.rb +41 -0
  13. data/lib/xccache/command/pkg/build.rb +30 -0
  14. data/lib/xccache/command/pkg.rb +1 -0
  15. data/lib/xccache/command/remote/pull.rb +15 -0
  16. data/lib/xccache/command/remote/push.rb +15 -0
  17. data/lib/xccache/command/remote.rb +39 -0
  18. data/lib/xccache/command/rollback.rb +14 -0
  19. data/lib/xccache/command/use.rb +19 -0
  20. data/lib/xccache/command.rb +27 -1
  21. data/lib/xccache/core/cacheable.rb +28 -0
  22. data/lib/xccache/core/config.rb +110 -0
  23. data/lib/xccache/core/error.rb +7 -0
  24. data/lib/xccache/core/git.rb +27 -0
  25. data/lib/xccache/core/hash.rb +21 -0
  26. data/lib/xccache/core/lockfile.rb +40 -0
  27. data/lib/xccache/core/log.rb +51 -0
  28. data/lib/xccache/core/parallel.rb +10 -0
  29. data/lib/xccache/core/sh.rb +51 -0
  30. data/lib/xccache/core/syntax/hash.rb +31 -0
  31. data/lib/xccache/core/syntax/json.rb +16 -0
  32. data/lib/xccache/core/syntax/plist.rb +17 -0
  33. data/lib/xccache/core/syntax/yml.rb +16 -0
  34. data/lib/xccache/core/syntax.rb +1 -0
  35. data/lib/xccache/core/system.rb +62 -0
  36. data/lib/xccache/core.rb +1 -0
  37. data/lib/xccache/installer/build.rb +23 -0
  38. data/lib/xccache/installer/rollback.rb +37 -0
  39. data/lib/xccache/installer/use.rb +28 -0
  40. data/lib/xccache/installer.rb +113 -0
  41. data/lib/xccache/main.rb +3 -1
  42. data/lib/xccache/spm/build.rb +53 -0
  43. data/lib/xccache/spm/desc/base.rb +68 -0
  44. data/lib/xccache/spm/desc/dep.rb +40 -0
  45. data/lib/xccache/spm/desc/desc.rb +126 -0
  46. data/lib/xccache/spm/desc/product.rb +36 -0
  47. data/lib/xccache/spm/desc/target/binary.rb +8 -0
  48. data/lib/xccache/spm/desc/target/macro.rb +8 -0
  49. data/lib/xccache/spm/desc/target.rb +164 -0
  50. data/lib/xccache/spm/desc.rb +1 -0
  51. data/lib/xccache/spm/macro.rb +44 -0
  52. data/lib/xccache/spm/mixin.rb +12 -0
  53. data/lib/xccache/spm/pkg/base.rb +107 -0
  54. data/lib/xccache/spm/pkg/umbrella/build.rb +30 -0
  55. data/lib/xccache/spm/pkg/umbrella/cachemap.rb +110 -0
  56. data/lib/xccache/spm/pkg/umbrella/descs.rb +35 -0
  57. data/lib/xccache/spm/pkg/umbrella/manifest.rb +83 -0
  58. data/lib/xccache/spm/pkg/umbrella/viz.rb +40 -0
  59. data/lib/xccache/spm/pkg/umbrella/xcconfigs.rb +31 -0
  60. data/lib/xccache/spm/pkg/umbrella.rb +91 -0
  61. data/lib/xccache/spm/pkg.rb +1 -0
  62. data/lib/xccache/spm/xcframework/metadata.rb +41 -0
  63. data/lib/xccache/spm/xcframework/slice.rb +180 -0
  64. data/lib/xccache/spm/xcframework/xcframework.rb +56 -0
  65. data/lib/xccache/spm/xcframework.rb +2 -0
  66. data/lib/xccache/spm.rb +1 -0
  67. data/lib/xccache/storage/base.rb +26 -0
  68. data/lib/xccache/storage/git.rb +46 -0
  69. data/lib/xccache/storage/s3.rb +53 -0
  70. data/lib/xccache/storage.rb +1 -0
  71. data/lib/xccache/swift/sdk.rb +49 -0
  72. data/lib/xccache/swift/swiftc.rb +16 -0
  73. data/lib/xccache/utils/template.rb +21 -0
  74. data/lib/xccache/xcodeproj/build_configuration.rb +20 -0
  75. data/lib/xccache/xcodeproj/config.rb +9 -0
  76. data/lib/xccache/xcodeproj/file_system_synchronized_root_group.rb +17 -0
  77. data/lib/xccache/xcodeproj/group.rb +26 -0
  78. data/lib/xccache/xcodeproj/pkg.rb +73 -0
  79. data/lib/xccache/xcodeproj/pkg_product_dependency.rb +19 -0
  80. data/lib/xccache/xcodeproj/project.rb +83 -0
  81. data/lib/xccache/xcodeproj/target.rb +52 -0
  82. data/lib/xccache/xcodeproj.rb +2 -0
  83. metadata +107 -2
@@ -1,8 +1,38 @@
1
+ require "xccache/spm"
2
+
1
3
  module XCCache
2
4
  class Command
3
5
  class Pkg < Command
4
6
  class Build < Pkg
5
7
  self.summary = "Build a Swift package into an xcframework"
8
+ def self.options
9
+ [
10
+ *Options.build_options,
11
+ ["--out=foo", "Output directory for the xcframework"],
12
+ ["--checksum/no-checksum", "Whether to include checksum to the binary name"],
13
+ ].concat(super)
14
+ end
15
+ self.arguments = [
16
+ CLAide::Argument.new("TARGET", false, true),
17
+ ]
18
+
19
+ def initialize(argv)
20
+ super
21
+ @targets = argv.arguments!
22
+ @out_dir = argv.option("out")
23
+ @include_checksum = argv.flag?("checksum")
24
+ end
25
+
26
+ def run
27
+ pkg = SPM::Package.new
28
+ pkg.build(
29
+ targets: @targets,
30
+ config: @config,
31
+ out_dir: @out_dir,
32
+ checksum: @include_checksum,
33
+ **@build_options,
34
+ )
35
+ end
6
36
  end
7
37
  end
8
38
  end
@@ -1,3 +1,4 @@
1
+ require_relative "base"
1
2
  require "xccache/command/pkg/build"
2
3
 
3
4
  module XCCache
@@ -0,0 +1,15 @@
1
+ require "xccache/spm"
2
+
3
+ module XCCache
4
+ class Command
5
+ class Remote < Command
6
+ class Pull < Remote
7
+ self.summary = "Pulling cache to local"
8
+
9
+ def run
10
+ storage.pull
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require "xccache/spm"
2
+
3
+ module XCCache
4
+ class Command
5
+ class Remote < Command
6
+ class Push < Remote
7
+ self.summary = "Pushing cache to remote"
8
+
9
+ def run
10
+ storage.push
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,39 @@
1
+ require_relative "base"
2
+ require "xccache/storage"
3
+ require "xccache/command/remote/pull"
4
+ require "xccache/command/remote/push"
5
+
6
+ module XCCache
7
+ class Command
8
+ class Remote < Command
9
+ self.abstract_command = true
10
+ self.summary = "Working with remote cache"
11
+ def self.options
12
+ [
13
+ ["--branch=foo", "Cache branch (if using git) (default: main)"],
14
+ ].concat(super)
15
+ end
16
+
17
+ def initialize(argv)
18
+ super
19
+ @branch = argv.option("branch", "main")
20
+ end
21
+
22
+ def storage
23
+ @storage ||= create_storage
24
+ end
25
+
26
+ private
27
+
28
+ def create_storage
29
+ remote_config = config.remote_config
30
+ if (remote = remote_config["git"])
31
+ return GitStorage.new(branch: @branch, remote: remote)
32
+ elsif (s3_config = remote_config["s3"])
33
+ return S3Storage.new(uri: s3_config["uri"], creds_path: s3_config["creds"])
34
+ end
35
+ Storage.new
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,14 @@
1
+ require "xccache/installer"
2
+ require_relative "base"
3
+
4
+ module XCCache
5
+ class Command
6
+ class Rollback < Command
7
+ self.summary = "Roll back prebuilt cache for packages"
8
+
9
+ def run
10
+ Installer::Rollback.new(ctx: self).install!
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ require "xccache/installer"
2
+ require_relative "base"
3
+
4
+ module XCCache
5
+ class Command
6
+ class Use < Command
7
+ self.summary = "Use prebuilt cache for packages"
8
+ def self.options
9
+ [
10
+ *Options.install_options,
11
+ ].concat(super)
12
+ end
13
+
14
+ def run
15
+ Installer::Use.new(ctx: self).install!
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,10 +1,36 @@
1
1
  require "claide"
2
+ require "xccache/core/config"
3
+ require "xccache/swift/sdk"
2
4
 
3
5
  module XCCache
4
6
  class Command < CLAide::Command
5
- require "xccache/command/pkg"
7
+ include Config::Mixin
8
+ Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
6
9
 
7
10
  self.abstract_command = true
11
+ self.default_subcommand = "use"
8
12
  self.summary = "xccache - a build caching tool"
13
+
14
+ attr_reader :install_options, :build_options
15
+
16
+ def initialize(argv)
17
+ super
18
+ config.verbose = verbose unless verbose.nil?
19
+ @install_options = {
20
+ :sdks => str_to_sdks(argv.option("sdk")),
21
+ :skip_resolving_dependencies => argv.flag?("skip-resolving-dependencies"),
22
+ }
23
+ @build_options = {
24
+ **@install_options,
25
+ :config => argv.option("config"),
26
+ :recursive => argv.flag?("recursive"),
27
+ :merge_slices => argv.flag?("merge-slices", true),
28
+ :library_evolution => argv.flag?("library-evolution"),
29
+ }
30
+ end
31
+
32
+ def str_to_sdks(str)
33
+ (str || config.default_sdk).split(",").map { |s| Swift::Sdk.new(s) }
34
+ end
9
35
  end
10
36
  end
@@ -0,0 +1,28 @@
1
+ module XCCache
2
+ module Cacheable
3
+ def cacheable(*method_names)
4
+ method_names.each do |method_name|
5
+ const_get(__cacheable_module_name).class_eval do
6
+ define_method(method_name) do |*args, **kwargs|
7
+ @_cache ||= {}
8
+ @_cache[method_name] ||= {}
9
+ @_cache[method_name][args.hash | kwargs.hash] ||=
10
+ method(method_name).super_method.call(*args, **kwargs)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ def __cacheable_module_name
17
+ "#{name}_Cacheable".gsub(':', '_')
18
+ end
19
+
20
+ def self.included(base)
21
+ base.extend(self)
22
+
23
+ module_name = base.send(:__cacheable_module_name)
24
+ remove_const(module_name) if const_defined?(module_name)
25
+ base.prepend(const_set(module_name, Module.new))
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,110 @@
1
+ require "xcodeproj"
2
+ require "xccache/core/syntax/yml"
3
+
4
+ module XCCache
5
+ class Config < YAMLRepresentable
6
+ module Mixin
7
+ def config
8
+ Config.instance
9
+ end
10
+ end
11
+
12
+ def self.instance
13
+ @instance ||= new(Pathname("xccache.yml").expand_path)
14
+ end
15
+
16
+ attr_accessor :verbose
17
+ alias verbose? verbose
18
+
19
+ # To distinguish if it's within an installation, or standalone like `xccache pkg build`
20
+ attr_accessor :in_installation
21
+ alias in_installation? in_installation
22
+
23
+ def sandbox
24
+ @sandbox = Dir.prepare("xccache").expand_path
25
+ end
26
+
27
+ def spm_sandbox
28
+ @spm_sandbox ||= Dir.prepare(sandbox / "packages").expand_path
29
+ end
30
+
31
+ def spm_local_pkgs_dir
32
+ @spm_local_pkgs_dir ||= Dir.prepare(spm_sandbox / "local")
33
+ end
34
+
35
+ def spm_xcconfig_dir
36
+ @spm_xcconfig_dir ||= Dir.prepare(spm_sandbox / "xcconfigs")
37
+ end
38
+
39
+ def spm_repo_dir
40
+ @spm_repo_dir ||= Dir.prepare(Pathname("~/.xccache/default").expand_path)
41
+ end
42
+
43
+ def spm_binaries_dir
44
+ @spm_binaries_dir ||= Dir.prepare(spm_umbrella_sandbox / "binaries")
45
+ end
46
+
47
+ def spm_build_dir
48
+ @spm_build_dir ||= spm_umbrella_sandbox / ".build"
49
+ end
50
+
51
+ def spm_artifacts_dir
52
+ @spm_artifacts_dir ||= spm_build_dir / "artifacts"
53
+ end
54
+
55
+ def spm_umbrella_sandbox
56
+ @spm_umbrella_sandbox ||= Dir.prepare(spm_sandbox / "umbrella")
57
+ end
58
+
59
+ def spm_metadata_dir
60
+ @spm_metadata_dir ||= Dir.prepare(spm_sandbox / "metadata")
61
+ end
62
+
63
+ def lockfile
64
+ @lockfile ||= Lockfile.new(Pathname("xccache.lock").expand_path)
65
+ end
66
+
67
+ def cachemap
68
+ @cachemap ||= Cache::Cachemap.new(sandbox / "cachemap.json")
69
+ end
70
+
71
+ def projects
72
+ @projects ||= Pathname(".").glob("*.xcodeproj").map do |p|
73
+ Xcodeproj::Project.open(p)
74
+ end
75
+ end
76
+
77
+ def project_targets
78
+ projects.flat_map(&:targets)
79
+ end
80
+
81
+ def remote_config
82
+ raw["remote"] || {}
83
+ end
84
+
85
+ def ignore_list
86
+ raw["ignore"] || []
87
+ end
88
+
89
+ def ignore?(item)
90
+ return true if ignore_local? && lockfile.local_pkg_slugs.include?(item.split("/").first)
91
+ ignore_list.any? { |p| File.fnmatch(p, item) }
92
+ end
93
+
94
+ def ignore_local?
95
+ raw["ignore_local"]
96
+ end
97
+
98
+ def ignore_build_errors?
99
+ raw["ignore_build_errors"]
100
+ end
101
+
102
+ def keep_pkgs_in_project?
103
+ raw["keep_pkgs_in_project"]
104
+ end
105
+
106
+ def default_sdk
107
+ raw["default_sdk"] || "iphonesimulator"
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,7 @@
1
+ module XCCache
2
+ class BaseError < StandardError
3
+ end
4
+
5
+ class GeneralError < BaseError
6
+ end
7
+ end
@@ -0,0 +1,27 @@
1
+ module XCCache
2
+ class Git
3
+ attr_reader :root
4
+
5
+ def initialize(root)
6
+ @root = Pathname(root)
7
+ end
8
+
9
+ def run(*args, **kwargs)
10
+ Sh.run("git -C #{root}", *args, **kwargs)
11
+ end
12
+
13
+ def sha
14
+ run("rev-parse --short HEAD", capture: true, log_cmd: false)[0].strip
15
+ end
16
+
17
+ def clean?
18
+ status("--porcelain", capture: true, log_cmd: false)[0].empty?
19
+ end
20
+
21
+ %i[checkout fetch pull push clean add commit branch remote switch status].each do |name|
22
+ define_method(name) do |*args, **kwargs|
23
+ run(name, *args, **kwargs)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ class Hash
2
+ def deep_merge(other, uniq_block: nil, &block)
3
+ dup.deep_merge!(other, uniq_block: uniq_block, &block)
4
+ end
5
+
6
+ def deep_merge!(other, uniq_block: nil, &block)
7
+ merge!(other) do |key, this_val, other_val|
8
+ result = if this_val.is_a?(Hash) && other_val.is_a?(Hash)
9
+ this_val.deep_merge(other_val, uniq_block: uniq_block, &block)
10
+ elsif this_val.is_a?(Array) && other_val.is_a?(Array)
11
+ this_val + other_val
12
+ elsif block_given?
13
+ block.call(key, this_val, other_val)
14
+ else
15
+ other_val
16
+ end
17
+ next result if uniq_block.nil? || !result.is_a?(Array)
18
+ result.reverse.uniq(&uniq_block).reverse # prefer updates
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,40 @@
1
+ require "xccache/core/syntax/json"
2
+
3
+ module XCCache
4
+ class Lockfile < JSONRepresentable
5
+ def hash_for_project(project)
6
+ raw[project.display_name] ||= {}
7
+ end
8
+
9
+ def product_dependencies_by_targets
10
+ @product_dependencies_by_targets ||= raw.values.map { |h| h["dependencies"] }.reduce { |acc, h| acc.merge(h) }
11
+ end
12
+
13
+ def deep_merge!(hash)
14
+ keys = ["repositoryURL", "path_from_root", "relative_path"]
15
+ raw.deep_merge!(hash, uniq_block: proc do |h|
16
+ h.is_a?(Hash) && (k = keys.find { |k| h.key?(k) }) ? h[k] : h
17
+ end)
18
+ end
19
+
20
+ def pkgs
21
+ @pkgs ||= raw.values.flat_map { |h| h["packages"] || [] }
22
+ end
23
+
24
+ def local_pkgs
25
+ @local_pkgs ||= pkgs.select { |h| h.key?("relative_path") || h.key?("path") }
26
+ end
27
+
28
+ def local_pkg_slugs
29
+ @local_pkg_slugs ||= local_pkgs.map { |h| File.basename(h["relative_path"] || h["path"]) }.uniq
30
+ end
31
+
32
+ def product_dependencies
33
+ @product_dependencies ||= product_dependencies_by_targets.values.flatten.uniq
34
+ end
35
+
36
+ def targets_data
37
+ @targets_data ||= product_dependencies_by_targets.transform_keys { |k| "#{k}.xccache" }
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,51 @@
1
+ require "xccache/core/config"
2
+ require "colored2"
3
+
4
+ module XCCache
5
+ module UI
6
+ @indent = 0
7
+
8
+ class << self
9
+ include Config::Mixin
10
+ attr_accessor :indent
11
+
12
+ def section(title, timing: false)
13
+ start = Time.new if timing
14
+ UI.puts(title)
15
+ self.indent += 2
16
+ res = yield if block_given?
17
+ self.indent -= 2
18
+ if timing
19
+ duration = (Time.new - start).to_i
20
+ duration = if duration < 60 then "#{duration}s"
21
+ elsif duration < 60 * 60 then "#{duration / 60}m"
22
+ else
23
+ "#{duration / 3600}h"
24
+ end
25
+ UI.puts("-> Finished: #{title.dark} (#{duration})")
26
+ end
27
+ res
28
+ end
29
+
30
+ def message(message)
31
+ UI.puts(message) if config.verbose?
32
+ end
33
+
34
+ def info(message)
35
+ UI.puts(message)
36
+ end
37
+
38
+ def warn(message)
39
+ UI.puts(message.yellow)
40
+ end
41
+
42
+ def error(message)
43
+ UI.puts("[ERROR] #{message}".red)
44
+ end
45
+
46
+ def puts(message)
47
+ $stdout.puts("#{' ' * self.indent}#{message}")
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,10 @@
1
+ require "parallel"
2
+
3
+ class Array
4
+ def parallel_map(options = {})
5
+ # By default, use in_threads (IO-bound tasks)
6
+ default = {}
7
+ default[:in_threads] = Parallel.processor_count unless options.key?(:in_processes)
8
+ Parallel.map(self, { **default, **options }) { |x| yield x if block_given? }
9
+ end
10
+ end
@@ -0,0 +1,51 @@
1
+ require "open3"
2
+ require "xccache/core/config"
3
+ require "xccache/core/log"
4
+ require "xccache/core/error"
5
+
6
+ module XCCache
7
+ class Sh
8
+ class ExecError < BaseError
9
+ end
10
+
11
+ class << self
12
+ include Config::Mixin
13
+
14
+ def capture_output(cmd)
15
+ run(cmd, capture: true, log_cmd: false)[0].strip
16
+ end
17
+
18
+ def run(*args, env: nil, **options)
19
+ cmd = args.join(" ")
20
+ UI.message("$ #{cmd}".cyan.dark) if config.verbose? && options[:log_cmd] != false
21
+
22
+ out, err = [], []
23
+ handle_out = proc do |line|
24
+ if options[:capture]
25
+ out << line
26
+ else
27
+ UI.puts line
28
+ end
29
+ end
30
+ handle_err = proc do |line|
31
+ if options[:capture]
32
+ err << line
33
+ else
34
+ UI.puts line.strip.yellow unless options[:suppress_err]&.match(line)
35
+ end
36
+ end
37
+
38
+ popen3_args = env ? [env, cmd] : [cmd]
39
+ Open3.popen3(*popen3_args) do |_stdin, stdout, stderr, wait_thr|
40
+ stdout_thread = Thread.new { stdout.each { |l| handle_out.call(l) } }
41
+ stderr_thread = Thread.new { stderr.each { |l| handle_err.call(l) } }
42
+ [stdout_thread, stderr_thread].each(&:join)
43
+ result = wait_thr.value
44
+ result.exitstatus
45
+ raise ExecError, "Command '#{cmd}' failed with status: #{result.exitstatus}" unless result.success?
46
+ end
47
+ [out.join("\n"), err.join("\n")]
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,31 @@
1
+ module XCCache
2
+ class HashRepresentable
3
+ attr_reader :path
4
+ attr_accessor :raw
5
+
6
+ def initialize(path, raw: nil)
7
+ @path = path
8
+ @raw = raw || load || {}
9
+ end
10
+
11
+ def load
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def merge!(other)
16
+ raw.merge!(other)
17
+ end
18
+
19
+ def save(to: nil)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def [](key)
24
+ raw[key]
25
+ end
26
+
27
+ def []=(key, value)
28
+ raw[key] = value
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ require "json"
2
+ require_relative "hash"
3
+
4
+ module XCCache
5
+ class JSONRepresentable < HashRepresentable
6
+ def load
7
+ JSON.parse(path.read) if path.exist?
8
+ rescue StandardError
9
+ {}
10
+ end
11
+
12
+ def save(to: nil)
13
+ (to || path).write(JSON.pretty_generate(raw))
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ require "cfpropertylist"
2
+ require_relative "hash"
3
+
4
+ module XCCache
5
+ class PlistRepresentable < HashRepresentable
6
+ def load
7
+ plist = CFPropertyList::List.new(file: path)
8
+ CFPropertyList.native_types(plist.value)
9
+ rescue StandardError
10
+ {}
11
+ end
12
+
13
+ def save(to: nil)
14
+ raise NotImplementedError
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ require "yaml"
2
+ require_relative "hash"
3
+
4
+ module XCCache
5
+ class YAMLRepresentable < HashRepresentable
6
+ def load
7
+ YAML.safe_load(path.read) if path.exist?
8
+ rescue StandardError
9
+ {}
10
+ end
11
+
12
+ def save(to: nil)
13
+ (to || path).write(raw.to_yaml)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1 @@
1
+ Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }