rom-files 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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__