rom-files 0.2.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.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +9 -0
  3. data/.gitignore +11 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +112 -0
  6. data/.rubocop_todo.yml +7 -0
  7. data/.ruby-version +1 -0
  8. data/.simplecov +6 -0
  9. data/.travis.yml +24 -0
  10. data/.yardopts +6 -0
  11. data/CHANGELOG.md +10 -0
  12. data/Gemfile +25 -0
  13. data/Guardfile +48 -0
  14. data/LICENSE.md +21 -0
  15. data/README.md +44 -0
  16. data/Rakefile +37 -0
  17. data/Roadmap.taskpaper +44 -0
  18. data/bin/_guard-core +21 -0
  19. data/bin/console +7 -0
  20. data/bin/guard +21 -0
  21. data/bin/rake +21 -0
  22. data/bin/rspec +21 -0
  23. data/bin/yard +21 -0
  24. data/bin/yardoc +21 -0
  25. data/bin/yri +21 -0
  26. data/lib/dry/types/pathname.rb +25 -0
  27. data/lib/pathname/extensions.rb +32 -0
  28. data/lib/pathname/extensions/constants.rb +12 -0
  29. data/lib/pathname/extensions/explode.rb +27 -0
  30. data/lib/pathname/extensions/ext.rb +28 -0
  31. data/lib/pathname/extensions/partial.rb +30 -0
  32. data/lib/pathname/extensions/pathmap.rb +128 -0
  33. data/lib/rom-files.rb +3 -0
  34. data/lib/rom/files.rb +28 -0
  35. data/lib/rom/files/associations.rb +6 -0
  36. data/lib/rom/files/associations/many_to_many.rb +12 -0
  37. data/lib/rom/files/associations/many_to_one.rb +12 -0
  38. data/lib/rom/files/associations/one_to_many.rb +12 -0
  39. data/lib/rom/files/associations/one_to_one.rb +12 -0
  40. data/lib/rom/files/attribute.rb +21 -0
  41. data/lib/rom/files/commands/create.rb +18 -0
  42. data/lib/rom/files/commands/delete.rb +17 -0
  43. data/lib/rom/files/commands/update.rb +17 -0
  44. data/lib/rom/files/connection.rb +120 -0
  45. data/lib/rom/files/constants.rb +16 -0
  46. data/lib/rom/files/dataset.rb +89 -0
  47. data/lib/rom/files/dataset/filtering.rb +96 -0
  48. data/lib/rom/files/dataset/mime_type.rb +44 -0
  49. data/lib/rom/files/dataset/paths.rb +79 -0
  50. data/lib/rom/files/dataset/sorting.rb +27 -0
  51. data/lib/rom/files/extensions.rb +30 -0
  52. data/lib/rom/files/extensions/gem.rb +29 -0
  53. data/lib/rom/files/extensions/gem/relations/documentations.rb +18 -0
  54. data/lib/rom/files/extensions/gem/relations/executables.rb +20 -0
  55. data/lib/rom/files/extensions/gem/relations/implementations.rb +29 -0
  56. data/lib/rom/files/extensions/gem/relations/specifications.rb +29 -0
  57. data/lib/rom/files/extensions/markdown/attributes_inferrer.rb +20 -0
  58. data/lib/rom/files/extensions/markdown/types.rb +18 -0
  59. data/lib/rom/files/extensions/markup/attributes_inferrer.rb +32 -0
  60. data/lib/rom/files/extensions/ruby/attributes_inferrer.rb +21 -0
  61. data/lib/rom/files/extensions/ruby/types.rb +45 -0
  62. data/lib/rom/files/extensions/text/attributes_inferrer.rb +20 -0
  63. data/lib/rom/files/gateway.rb +63 -0
  64. data/lib/rom/files/plugins/configuration/gem.rb +30 -0
  65. data/lib/rom/files/plugins/relation/instrumentation.rb +28 -0
  66. data/lib/rom/files/plugins/schema/contents.rb +72 -0
  67. data/lib/rom/files/plugins/schema/mime.rb +59 -0
  68. data/lib/rom/files/plugins/schema/shebang.rb +79 -0
  69. data/lib/rom/files/plugins/schema/stat.rb +108 -0
  70. data/lib/rom/files/relation.rb +143 -0
  71. data/lib/rom/files/schema.rb +77 -0
  72. data/lib/rom/files/schema/attributes_inferrer.rb +79 -0
  73. data/lib/rom/files/schema/inferrer.rb +36 -0
  74. data/lib/rom/files/types.rb +37 -0
  75. data/lib/rom/files/version.rb +7 -0
  76. data/rom-files.gemspec +48 -0
  77. data/spec/integration/rom/files/gateway_spec.rb +28 -0
  78. data/spec/integration/rom/files/gem_spec.rb +98 -0
  79. data/spec/integration/rom/files/relations_spec.rb +63 -0
  80. data/spec/integration/rom/files/schema/inferrer_spec.rb +77 -0
  81. data/spec/lib/dry/types/pathname_spec.rb +50 -0
  82. data/spec/lib/pathname/extensions/explode_spec.rb +25 -0
  83. data/spec/lib/pathname/extensions/ext_spec.rb +33 -0
  84. data/spec/lib/pathname/extensions/partial_spec.rb +17 -0
  85. data/spec/lib/pathname/extensions/pathmap_spec.rb +147 -0
  86. data/spec/lib/rom/files/attribute_spec.rb +43 -0
  87. data/spec/lib/rom/files/connection_spec.rb +76 -0
  88. data/spec/lib/rom/files/dataset/inside_spec.rb +22 -0
  89. data/spec/lib/rom/files/dataset/mime_type_spec.rb +23 -0
  90. data/spec/lib/rom/files/dataset/recursive_question_mark_spec.rb +42 -0
  91. data/spec/lib/rom/files/dataset/recursive_spec.rb +29 -0
  92. data/spec/lib/rom/files/dataset/reject_append_spec.rb +68 -0
  93. data/spec/lib/rom/files/dataset/reject_spec.rb +26 -0
  94. data/spec/lib/rom/files/dataset/select_append_spec.rb +69 -0
  95. data/spec/lib/rom/files/dataset/select_spec.rb +38 -0
  96. data/spec/lib/rom/files/dataset/sort_spec.rb +22 -0
  97. data/spec/lib/rom/files/dataset_spec.rb +52 -0
  98. data/spec/lib/rom/files/extensions/text/attributes_inferrer_spec.rb +54 -0
  99. data/spec/lib/rom/files/gateway_spec.rb +39 -0
  100. data/spec/lib/rom/files/plugins/schema/contents_spec.rb +66 -0
  101. data/spec/lib/rom/files/plugins/schema/mime_spec.rb +66 -0
  102. data/spec/lib/rom/files/plugins/schema/stat_spec.rb +109 -0
  103. data/spec/lib/rom/files/relation/pluck_spec.rb +20 -0
  104. data/spec/lib/rom/files/relation/reject_spec.rb +22 -0
  105. data/spec/lib/rom/files/relation/select_spec.rb +35 -0
  106. data/spec/lib/rom/files/relation/sort_spec.rb +21 -0
  107. data/spec/lib/rom/files/relation/to_a_spec.rb +39 -0
  108. data/spec/lib/rom/files/relation_spec.rb +10 -0
  109. data/spec/lib/rom/files/schema/attributes_inferrer_spec.rb +45 -0
  110. data/spec/lib/rom/files/schema/inferrer_spec.rb +29 -0
  111. data/spec/lib/rom/files/schema_spec.rb +109 -0
  112. data/spec/lib/rom/files/types/mime_type_spec.rb +9 -0
  113. data/spec/lib/rom/files/types/path_spec.rb +10 -0
  114. data/spec/lib/rom/files/types_spec.rb +6 -0
  115. data/spec/shared/rom/files/files_setup.rb +19 -0
  116. data/spec/shared/rom/files/filesystem_setup.rb +10 -0
  117. data/spec/shared/rom/files/gateway_setup.rb +17 -0
  118. data/spec/shared/rom/files/media_dataset.rb +10 -0
  119. data/spec/shared/rom/files/media_files.rb +17 -0
  120. data/spec/shared/rom/files/media_relation.rb +30 -0
  121. data/spec/spec_helper.rb +10 -0
  122. metadata +421 -0
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'rom/relation'
5
+ require_relative 'attribute'
6
+ require_relative 'schema'
7
+ require_relative 'schema/inferrer'
8
+
9
+ module ROM
10
+ module Files
11
+ class Relation < ROM::Relation
12
+ extend Forwardable
13
+ include Enumerable
14
+ include Files
15
+
16
+ adapter :files
17
+ schema_attr_class Files::Attribute
18
+ schema_class Files::Schema
19
+ schema_inferrer Files::Schema::Inferrer.new.freeze
20
+
21
+ def initialize(*) # :nodoc:
22
+ super
23
+ @dataset = @dataset.with(row_proc: schema.row_proc) if schema
24
+ end
25
+
26
+ # @!attribute [r] dataset
27
+ # @return [Files::Dataset]
28
+ # @!attribute [r] schema
29
+ # @return [Files::Schema]
30
+
31
+ # @!group Reading
32
+
33
+ # @!method select(*patterns)
34
+ # @param (see Dataset#select)
35
+ # @return [Relation]
36
+ #
37
+ # @!method select_append(*patterns)
38
+ # @param (see Dataset#select_append)
39
+ # @return [Relation]
40
+ #
41
+ # @!method inside(*prefixes)
42
+ # @param (see Dataset#inside)
43
+ # @return [Relation]
44
+ #
45
+ # @!method recursive
46
+ # @return [Relation]
47
+ #
48
+ # @!method reject(*patterns)
49
+ # @param (see Dataset#reject)
50
+ # @return [Relation]
51
+ #
52
+ # @!method reject_append(*patterns)
53
+ # @param (see Dataset#reject_append)
54
+ # @return [Relation]
55
+ #
56
+ # @!method sort(sorting = :to_s)
57
+ # @param (see Dataset#sort)
58
+ # @return [Relation]
59
+ #
60
+ # @!method restrict(criteria = nil)
61
+ # @param (see Dataset#restrict)
62
+ # @return [Relation]
63
+ #
64
+ # @!method join(*args)
65
+ # @param (see Dataset#join)
66
+ # @return [Relation]
67
+ forward :select, :select_append, :reject, :reject_append,
68
+ :inside, :recursive, :sort, :restrict, :join
69
+
70
+ # @!method mime_type
71
+ # @return [MIME::Type, nil]
72
+ #
73
+ # @see Dataset#mime_type
74
+ #
75
+ # @!method pluck
76
+ # @return [Array]
77
+ #
78
+ # @see Dataset#pluck
79
+ #
80
+ # @!method recursive?
81
+ # @return [Boolean]
82
+ #
83
+ # @see Dataset#recursive?
84
+ def_instance_delegators :dataset, :mime_type, :recursive?, :pluck
85
+
86
+ # Project a relation with provided attribute names
87
+ #
88
+ # @param names [Array<Symbol>] A list with attribute names
89
+ #
90
+ # @return [Relation]
91
+ #
92
+ # @api public
93
+ def project(*names)
94
+ schema.project(*names).(self)
95
+ end
96
+
97
+ # Return relation count
98
+ #
99
+ # @example
100
+ # users.count
101
+ # # => 12
102
+ #
103
+ # @return [Integer]
104
+ #
105
+ # @api public
106
+ def count
107
+ dataset.count
108
+ end
109
+
110
+ # @!group Writing
111
+
112
+ def create(tuple)
113
+ dataset.write(
114
+ identify(tuple),
115
+ contents_for(tuple)
116
+ )
117
+ end
118
+ alias << create
119
+
120
+ def update(tuple, attributes = {})
121
+ dataset.write(
122
+ identify(tuple),
123
+ contents_for(tuple.merge(attributes))
124
+ )
125
+ end
126
+
127
+ def delete(tuple)
128
+ dataset.delete(identify(tuple))
129
+ end
130
+
131
+ # @!method identify(tuple)
132
+ # @param (see Schema#identify)
133
+ # @return (see Schema#identify)
134
+ # @see Schema#identify
135
+ #
136
+ # @!method contents_for(tuple)
137
+ # @param (see Schema#contents_for)
138
+ # @return (see Schema#contents_for)
139
+ # @see Schema#contents_for
140
+ def_instance_delegators :schema, :identify, :contents_for
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/schema'
4
+ require_relative 'types'
5
+ require_relative 'associations'
6
+
7
+ module ROM
8
+ module Files
9
+ # Specialized schema for files adapter
10
+ #
11
+ # @api public
12
+ class Schema < ROM::Schema
13
+ # Abstract method for creating a new relation based on schema definition
14
+ #
15
+ # This can be used by views to generate a new relation automatically.
16
+ # In example a schema can project a relation, join any additional relations
17
+ # if it include_patterns attributes from other relations etc.
18
+ #
19
+ # Default implementation is a no-op and it simply returns back untouched relation
20
+ #
21
+ # @param [Relation] relation
22
+ #
23
+ # @return [Relation]
24
+ #
25
+ # @api public
26
+ def call(relation)
27
+ relation.new(relation.dataset.with(row_proc: row_proc), schema: self)
28
+ end
29
+
30
+ # @return [Method, Proc]
31
+ def row_proc
32
+ method(:load_attributes)
33
+ end
34
+
35
+ # @param pathname [Pathname]
36
+ # @return [Hash{Symbol => Object}]
37
+ def load_attributes(pathname)
38
+ attributes.each_with_object({}) do |attribute, result|
39
+ result[attribute.name] = attribute.(pathname)
40
+ result
41
+ end
42
+ end
43
+
44
+ # @param tuple [Hash]
45
+ # @return [Pathname]
46
+ def identify(tuple)
47
+ path = (primary_key_names || [ID]).map do |name|
48
+ tuple[name]
49
+ end.compact
50
+ return unless path.any?
51
+ Pathname(path.shift).join(*path)
52
+ end
53
+
54
+ # @param tuple [Hash]
55
+ # @return [String]
56
+ def contents_for(tuple)
57
+ contents = attributes.each_with_object([]) do |attr, result|
58
+ result << tuple[attr.name] if attr.meta[DATA]
59
+ end.compact
60
+ contents.join if contents.any?
61
+ end
62
+
63
+ # Internal hook used during setup process
64
+ #
65
+ # @see Schema#finalize_associations!
66
+ #
67
+ # @api private
68
+ def finalize_associations!(relations:)
69
+ super do
70
+ associations.map do |definition|
71
+ Files::Associations.const_get(definition.type).new(definition, relations)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/core/class_attributes'
4
+ require 'rom/files/constants'
5
+ require 'rom/files/attribute'
6
+ require 'mime/types/full'
7
+
8
+ module ROM
9
+ module Files
10
+ class Schema < ROM::Schema
11
+ # @api private
12
+ class AttributesInferrer
13
+ extend Dry::Core::ClassAttributes
14
+ extend Initializer
15
+
16
+ KNOWN_COLUMNS = [ID].freeze
17
+
18
+ # @!method self.registry
19
+ # @return [Hash{String => AttributesInferrer}]
20
+ defines :registry, :attr_class
21
+ registry Hash.new(new.freeze)
22
+ attr_class Files::Attribute
23
+
24
+ # @param type [String]
25
+ # @param inferrer [AttributesInferrer]
26
+ # @return [AttributesInferrer]
27
+ def self.register(type, inferrer)
28
+ registry[type] = inferrer
29
+ end
30
+
31
+ def self.registered?(type)
32
+ registry.key?(type)
33
+ end
34
+
35
+ # @param mime_type [String]
36
+ # @return [AttributesInferrer]
37
+ def self.[](mime_type)
38
+ registry[mime_type]
39
+ end
40
+
41
+ # @!attribute [r] attr_class
42
+ # @return [Class(Files::Attribute)]
43
+ option :attr_class, default: -> { self.class.attr_class }
44
+
45
+ # @param schema [ROM::Files::Schema]
46
+ # @param gateway [ROM::Files::Gateway]
47
+ #
48
+ # @api private
49
+ def call(schema, gateway)
50
+ inferred = infer_attributes(schema, gateway)
51
+
52
+ missing = columns - inferred.map { |attr| attr.meta[:name] }
53
+
54
+ [inferred, missing]
55
+ end
56
+
57
+ undef :with
58
+
59
+ # @return [Files::Attribute]
60
+ def build(type, name, schema)
61
+ attr_class.new(type.meta(name: name, source: schema.name))
62
+ end
63
+
64
+ # @return [Array<Symbol>]
65
+ def columns
66
+ KNOWN_COLUMNS
67
+ end
68
+
69
+ def with(new_options)
70
+ self.class.new(options.merge(new_options))
71
+ end
72
+
73
+ def infer_attributes(schema, _gateway)
74
+ [build(Types::Path, ID, schema)]
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/schema'
4
+ require 'rom/schema/inferrer'
5
+ require_relative '../attribute'
6
+ require_relative 'attributes_inferrer'
7
+
8
+ module ROM
9
+ module Files
10
+ class Schema < ROM::Schema
11
+ class Inferrer < ROM::Schema::Inferrer
12
+ # @!attribute [r] attributes_inferrer
13
+ # @return [Array(Array, Array)]
14
+ attributes_inferrer ->(schema, gateway, options) do
15
+ dataset = gateway.dataset(schema.name.dataset)
16
+ inferrer = if dataset.mime_type
17
+ if AttributesInferrer.registered?(content_type = dataset.mime_type.content_type)
18
+ AttributesInferrer[content_type].with(options)
19
+ elsif AttributesInferrer.registered?(media_type = dataset.mime_type.media_type)
20
+ AttributesInferrer[media_type].with(options)
21
+ else
22
+ AttributesInferrer.new(**options)
23
+ end
24
+ else
25
+ AttributesInferrer.new(**options)
26
+ end
27
+ inferrer.(schema, gateway)
28
+ end
29
+
30
+ # @!attribute [r] attr_class
31
+ # @return [Class]
32
+ attr_class Files::Attribute
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/types/pathname'
4
+ require 'rom/types'
5
+ require 'mime/types'
6
+
7
+ module ROM
8
+ module Files
9
+ module Types
10
+ include ROM::Types
11
+
12
+ Path = Types::Pathname.meta(primary_key: true)
13
+
14
+ FileStat = Dry::Types::Definition[File::Stat].new(File::Stat)
15
+ FileType = Coercible::String.enum('file', 'directory', 'characterSpecial',
16
+ 'blockSpecial', 'fifo', 'link', 'socket', 'unknown')
17
+ MimeType = Dry::Types::Definition[MIME::Type].new(MIME::Type).optional.constructor do |type|
18
+ MIME::Types[type].first
19
+ end
20
+
21
+ # Define a foreign key attribute type
22
+ #
23
+ # @example with default type
24
+ # attribute :spec_file, Types.ForeignKey(:spec_files)
25
+ #
26
+ # @example with a custom path map
27
+ # attribute :spec_file, Types.ForeignKey(:spec_files, key: ->(path) { path.pathmap('spec/%X_spec.rb') })
28
+ #
29
+ # @return [Dry::Types::Definition]
30
+ #
31
+ # @api public
32
+ def self.ForeignKey(relation, type = Types::Pathname, map: ->(pathname) { pathname })
33
+ super(relation, type.meta(__proc__: map))
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Files
5
+ VERSION = '0.2.0'
6
+ end
7
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'rom/files/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'rom-files'
9
+ spec.version = ROM::Files::VERSION
10
+ spec.authors = ['Alex Semyonov', 'Héctor Ramón']
11
+ spec.email = %w[alex@cerebelo.info hector0193@gmail.com]
12
+
13
+ spec.summary = 'Files adapter for ROM'
14
+ spec.description = spec.summary
15
+ spec.homepage = 'https://github.com/alsemyonov/rom-files'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0")
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_runtime_dependency 'mime-types', '~> 3.1'
24
+ spec.add_runtime_dependency 'rom', '~> 4.1'
25
+
26
+ # Extensions
27
+ spec.add_development_dependency 'kramdown'
28
+ spec.add_development_dependency 'parser'
29
+ spec.add_development_dependency 'xdg'
30
+
31
+ # Dependencies
32
+ spec.add_development_dependency 'bundler'
33
+
34
+ # Pipeline
35
+ spec.add_development_dependency 'rake', '~> 12.2'
36
+
37
+ # Testing
38
+ spec.add_development_dependency 'rspec', '~> 3.7'
39
+ spec.add_development_dependency 'rspec-its'
40
+ spec.add_development_dependency 'rubocop', '~> 0.51'
41
+ spec.add_development_dependency 'simplecov', '~> 0.15'
42
+
43
+ # Documentation
44
+ spec.add_development_dependency 'redcarpet'
45
+ spec.add_development_dependency 'yard', '~> 0.9'
46
+ spec.add_development_dependency 'yard-junk'
47
+ spec.add_development_dependency 'yardcheck'
48
+ end
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'rom-files'
5
+ require 'rspec'
6
+
7
+ RSpec.describe ROM::Files::Gateway, 'usage' do
8
+ subject(:gateway) { ROM::Files::Gateway.new(uri) }
9
+ let(:uri) { SPEC_ROOT.dirname }
10
+
11
+ example 'Obtaining datasets', :aggregate_failures do
12
+ lib = gateway.dataset(:lib).recursive
13
+
14
+ expect(lib).to be_a ROM::Files::Dataset
15
+ expect(gateway.dataset?(:lib)).to be true
16
+
17
+ puts "Library files:\n", lib.pluck(&:to_s)
18
+
19
+ spec = gateway.dataset(:spec).recursive
20
+
21
+ expect(spec).to be_a ROM::Files::Dataset
22
+ expect(gateway.dataset?(:spec)).to be true
23
+
24
+ p "Specifications:\n", spec.pluck(&:to_s)
25
+ end
26
+ end
27
+
28
+ require 'rspec/autorun' if $PROGRAM_NAME == __FILE__