dry-files 0.2.0 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b04750013e1faf98db463c2c346a5f43ed63f3dca9053a881082b02ac4c6e2a
4
- data.tar.gz: 1c3162ad5c735483ac4fbac02b588e111e55f92698682807dad68bf12503906a
3
+ metadata.gz: b9b554d01289fcefe3eb4159a0d832a55544de168d9df3d68db65d017526debd
4
+ data.tar.gz: cfe09c1716f6046421111bd848a81c9a0785b195f4a612c348c2054cdf6aa805
5
5
  SHA512:
6
- metadata.gz: e8a0a61aba226cc8f1f7dc01fb06a8c21e8c2405c131cae4e45689e551996fe2d72deae4b99b0e9e677aad294bdef1eca53183a75386344a89c136afd243784d
7
- data.tar.gz: d363a392c4427fe3b6b84349f5addcb839e7881bdcab9dcd7e6d32124632883ca490187d8e654b84ef0162a675cc25d2b928e9448504ef94132ad5076dc77110
6
+ metadata.gz: 2e6142b107d2deafc6bb1b98412fee75be75e05934e940fd8227abd8079cff9b4078d064a5e8ae90339b95a9852a189bab199d3ec5f4de5e6001e13b22be7cde
7
+ data.tar.gz: 53abdcbd822a4dbc78dac59308454fe46738e21448d3faeca7eae8e80d6a0725b06f7d614c4e736bba0107719c45229765a16ea84b529aad3a4445d882859500
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
2
2
 
3
+ ## 1.0.0 2022-11-04
4
+
5
+
6
+ ### Changed
7
+
8
+ - Bumped version to 1.0.0 (@solnic)
9
+
10
+ [Compare v0.3.0...v1.0.0](https://github.com/dry-rb/dry-files/compare/v0.3.0...v1.0.0)
11
+
12
+ ## 0.3.0 2022-09-19
13
+
14
+
15
+ ### Added
16
+
17
+ - Support for `inject_line_at_class_bottom` (via #13) (@jodosha)
18
+
19
+
20
+ [Compare v0.2.0...v0.3.0](https://github.com/dry-rb/dry-files/compare/v0.2.0...v0.3.0)
21
+
3
22
  ## 0.2.0 2022-07-10
4
23
 
5
24
 
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2021 dry-rb team
3
+ Copyright (c) 2015-2022 dry-rb team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
data/dry-files.gemspec CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.require_paths = ["lib"]
23
23
 
24
24
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
25
- spec.metadata["changelog_uri"] = "https://github.com/dry-rb/dry-files/blob/master/CHANGELOG.md"
25
+ spec.metadata["changelog_uri"] = "https://github.com/dry-rb/dry-files/blob/main/CHANGELOG.md"
26
26
  spec.metadata["source_code_uri"] = "https://github.com/dry-rb/dry-files"
27
27
  spec.metadata["bug_tracker_uri"] = "https://github.com/dry-rb/dry-files/issues"
28
28
 
@@ -30,7 +30,5 @@ Gem::Specification.new do |spec|
30
30
 
31
31
  # to update dependencies edit project.yml
32
32
 
33
- spec.add_development_dependency "rake", "~> 13.0"
34
33
  spec.add_development_dependency "rspec", "~> 3.10"
35
- spec.add_development_dependency "rubocop", "~> 1.12"
36
34
  end
@@ -46,7 +46,7 @@ module Dry
46
46
  # @raise [Dry::Files::IOError] in case of I/O error
47
47
  #
48
48
  # @since 0.1.0
49
- def open(path, mode = OPEN_MODE, *args, &blk)
49
+ def open(path, mode, *args, &blk)
50
50
  touch(path)
51
51
 
52
52
  with_error_handling do
@@ -345,16 +345,6 @@ module Dry
345
345
 
346
346
  private
347
347
 
348
- # @since 0.1.0
349
- # @api private
350
- OPEN_MODE = ::File::RDWR
351
- private_constant :OPEN_MODE
352
-
353
- # @since 0.1.0
354
- # @api private
355
- WRITE_MODE = (::File::CREAT | ::File::WRONLY | ::File::TRUNC).freeze
356
- private_constant :WRITE_MODE
357
-
358
348
  # Catch `SystemCallError` and re-raise a `Dry::Files::IOError`.
359
349
  #
360
350
  # `SystemCallError` is parent for all the `Errno::*` Ruby exceptions.
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "English"
4
3
  require "stringio"
5
4
 
6
5
  module Dry
@@ -215,8 +214,17 @@ module Dry
215
214
  #
216
215
  # @since 0.1.0
217
216
  # @api private
218
- def write(*content)
219
- @content = StringIO.new(content.join($RS))
217
+ def write(content)
218
+ content = case content
219
+ when String
220
+ content
221
+ when Array
222
+ array_to_string(content)
223
+ when NilClass
224
+ EMPTY_CONTENT
225
+ end
226
+
227
+ @content = StringIO.new(content)
220
228
  @mode = DEFAULT_FILE_MODE
221
229
  end
222
230
 
@@ -240,6 +248,14 @@ module Dry
240
248
  def executable?
241
249
  (mode & MODE_USER_EXECUTE).positive?
242
250
  end
251
+
252
+ # @since 0.3.0
253
+ # @api private
254
+ def array_to_string(content)
255
+ content.map do |line|
256
+ line.sub(NEW_LINE_MATCHER, EMPTY_CONTENT)
257
+ end.join(NEW_LINE) + NEW_LINE
258
+ end
243
259
  end
244
260
  end
245
261
  end
@@ -11,7 +11,7 @@ module Dry
11
11
  class MemoryFileSystem
12
12
  # @since 0.1.0
13
13
  # @api private
14
- EMPTY_CONTENT = nil
14
+ EMPTY_CONTENT = ""
15
15
  private_constant :EMPTY_CONTENT
16
16
 
17
17
  require_relative "./memory_file_system/node"
@@ -33,12 +33,18 @@ module Dry
33
33
  #
34
34
  # @param path [String] the target file
35
35
  # @yieldparam [Dry::Files::MemoryFileSystem::Node]
36
+ # @return [Dry::Files::MemoryFileSystem::Node]
36
37
  #
37
38
  # @since 0.1.0
38
39
  # @api private
39
- def open(path, *, &blk)
40
+ def open(path, *)
40
41
  file = touch(path)
41
- blk.call(file)
42
+
43
+ if block_given?
44
+ yield file
45
+ else
46
+ file
47
+ end
42
48
  end
43
49
 
44
50
  # Read file contents
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dry
4
4
  class Files
5
- VERSION = "0.2.0"
5
+ VERSION = "1.0.0"
6
6
  end
7
7
  end
data/lib/dry/files.rb CHANGED
@@ -14,6 +14,14 @@ module Dry
14
14
  require_relative "files/error"
15
15
  require_relative "files/adapter"
16
16
 
17
+ # @since 0.3.0
18
+ # @api public
19
+ OPEN_MODE = ::File::RDWR
20
+
21
+ # @since 0.3.0
22
+ # @api public
23
+ WRITE_MODE = (::File::CREAT | ::File::WRONLY | ::File::TRUNC).freeze
24
+
17
25
  # Creates a new instance
18
26
  #
19
27
  # Memory file system is experimental
@@ -111,6 +119,22 @@ module Dry
111
119
  adapter.pwd
112
120
  end
113
121
 
122
+ # Opens (or creates) a new file for both read/write operations
123
+ #
124
+ # @param path [String] the target file
125
+ # @param mode [String,Integer] Ruby file open mode
126
+ # @param args [Array<Object>] ::File.open args
127
+ # @param blk [Proc] the block to yield
128
+ #
129
+ # @yieldparam [File,Dry::Files::MemoryFileSystem::Node] the opened file
130
+ #
131
+ # @return [File,Dry::Files::MemoryFileSystem::Node] the opened file
132
+ #
133
+ # @raise [Dry::Files::IOError] in case of I/O error
134
+ def open(path, mode = OPEN_MODE, *args, &blk)
135
+ adapter.open(path, mode, *args, &blk)
136
+ end
137
+
114
138
  # Temporary changes the current working directory of the process to the
115
139
  # given path and yield the given block.
116
140
  #
@@ -657,14 +681,92 @@ module Dry
657
681
  # # end
658
682
  # # end
659
683
  def inject_line_at_block_bottom(path, target, *contents)
660
- content = adapter.readlines(path)
661
- starting = index(content, path, target)
662
- line = content[starting]
663
- size = line[SPACE_MATCHER].bytesize
664
- closing = (SPACE * size) +
665
- (target.match?(INLINE_OPEN_BLOCK_MATCHER) ? INLINE_CLOSE_BLOCK : CLOSE_BLOCK)
666
- ending = starting + index(content[starting..-CONTENT_OFFSET], path, closing)
667
- offset = SPACE * (content[ending][SPACE_MATCHER].bytesize + INDENTATION)
684
+ content = adapter.readlines(path)
685
+ starting = index(content, path, target)
686
+ line = content[starting]
687
+ delimiter = if line.match?(INLINE_OPEN_BLOCK_MATCHER)
688
+ INLINE_BLOCK_DELIMITER
689
+ else
690
+ BLOCK_DELIMITER
691
+ end
692
+ target = content[starting..]
693
+ ending = closing_block_index(target, starting, path, line, delimiter)
694
+ offset = SPACE * (content[ending][SPACE_MATCHER].bytesize + INDENTATION)
695
+
696
+ contents = Array(contents).flatten
697
+ contents = _offset_block_lines(contents, offset)
698
+
699
+ content.insert(ending, contents)
700
+ write(path, content)
701
+ end
702
+
703
+ # Inject `contents` in `path` at the bottom of the Ruby class that matches `target`.
704
+ # The given `contents` will appear at the BOTTOM of the Ruby class.
705
+ #
706
+ # @param path [String,Pathname] the path to file
707
+ # @param target [String,Regexp] the target matcher for Ruby class
708
+ # @param contents [String,Array<String>] the contents to inject
709
+ #
710
+ # @raise [Dry::Files::IOError] in case of I/O error
711
+ # @raise [Dry::Files::MissingTargetError] if `target` cannot be found in `path`
712
+ #
713
+ # @since 0.4.0
714
+ # @api public
715
+ #
716
+ # @example Inject a single line
717
+ # require "dry/files"
718
+ #
719
+ # files = Dry::Files.new
720
+ # path = "config/application.rb"
721
+ #
722
+ # File.read(path)
723
+ # # # frozen_string_literal: true
724
+ # #
725
+ # # class Application
726
+ # # end
727
+ #
728
+ # # inject a single line
729
+ # files.inject_line_at_class_bottom(path, /Application/, %(attr_accessor :name))
730
+ #
731
+ # File.read(path)
732
+ # # # frozen_string_literal: true
733
+ # #
734
+ # # class Application
735
+ # # attr_accessor :name
736
+ # # end
737
+ #
738
+ # @example Inject multiple lines
739
+ # require "dry/files"
740
+ #
741
+ # files = Dry::Files.new
742
+ # path = "math.rb"
743
+ #
744
+ # File.read(path)
745
+ # # # frozen_string_literal: true
746
+ # #
747
+ # # class Math
748
+ # # end
749
+ #
750
+ # # inject multiple lines
751
+ # files.inject_line_at_class_bottom(path,
752
+ # /Math/,
753
+ # ["def sum(a, b)", " a + b", "end"])
754
+ #
755
+ # File.read(path)
756
+ # # # frozen_string_literal: true
757
+ # #
758
+ # # class Math
759
+ # # def sum(a, b)
760
+ # # a + b
761
+ # # end
762
+ # # end
763
+ def inject_line_at_class_bottom(path, target, *contents)
764
+ content = adapter.readlines(path)
765
+ starting = index(content, path, target)
766
+ line = content[starting]
767
+ target = content[starting..]
768
+ ending = closing_class_index(target, starting, path, line, BLOCK_DELIMITER)
769
+ offset = SPACE * (content[ending][SPACE_MATCHER].bytesize + INDENTATION)
668
770
 
669
771
  contents = Array(contents).flatten
670
772
  contents = _offset_block_lines(contents, offset)
@@ -736,11 +838,38 @@ module Dry
736
838
 
737
839
  private
738
840
 
841
+ # @since 0.3.0
842
+ # @api private
843
+ class Delimiter
844
+ # @since 0.3.0
845
+ # @api private
846
+ attr_reader :opening, :closing
847
+
848
+ # @since 0.3.0
849
+ # @api private
850
+ def initialize(name, opening, closing)
851
+ @name = name
852
+ @opening = opening
853
+ @closing = closing
854
+ freeze
855
+ end
856
+ end
857
+
739
858
  # @since 0.1.0
740
859
  # @api private
741
860
  NEW_LINE = $/ # rubocop:disable Style/SpecialGlobalVars
742
861
  private_constant :NEW_LINE
743
862
 
863
+ # @since 0.3.0
864
+ # @api private
865
+ NEW_LINE_MATCHER = /#{NEW_LINE}\z/.freeze
866
+ private_constant :NEW_LINE_MATCHER
867
+
868
+ # @since 0.3.0
869
+ # @api private
870
+ EMPTY_LINE = /\A\z/.freeze
871
+ private_constant :EMPTY_LINE
872
+
744
873
  # @since 0.1.0
745
874
  # @api private
746
875
  CONTENT_OFFSET = 1
@@ -761,21 +890,42 @@ module Dry
761
890
  SPACE_MATCHER = /\A[[:space:]]*/.freeze
762
891
  private_constant :SPACE_MATCHER
763
892
 
764
- # @since 0.1.0
893
+ # @since 0.3.0
765
894
  # @api private
766
- INLINE_OPEN_BLOCK_MATCHER = "{"
767
- private_constant :INLINE_OPEN_BLOCK_MATCHER
895
+ INLINE_OPEN_BLOCK = "{"
896
+ private_constant :INLINE_OPEN_BLOCK
768
897
 
769
898
  # @since 0.1.0
770
899
  # @api private
771
900
  INLINE_CLOSE_BLOCK = "}"
772
901
  private_constant :INLINE_CLOSE_BLOCK
773
902
 
903
+ # @since 0.3.0
904
+ # @api private
905
+ OPEN_BLOCK = "do"
906
+ private_constant :OPEN_BLOCK
907
+
774
908
  # @since 0.1.0
775
909
  # @api private
776
910
  CLOSE_BLOCK = "end"
777
911
  private_constant :CLOSE_BLOCK
778
912
 
913
+ # @since 0.1.0
914
+ # @api private
915
+ INLINE_OPEN_BLOCK_MATCHER = INLINE_CLOSE_BLOCK
916
+ private_constant :INLINE_OPEN_BLOCK_MATCHER
917
+
918
+ # @since 0.3.0
919
+ # @api private
920
+ INLINE_BLOCK_DELIMITER = Delimiter.new("InlineBlockDelimiter",
921
+ INLINE_OPEN_BLOCK, INLINE_CLOSE_BLOCK)
922
+ private_constant :INLINE_BLOCK_DELIMITER
923
+
924
+ # @since 0.3.0
925
+ # @api private
926
+ BLOCK_DELIMITER = Delimiter.new("BlockDelimiter", OPEN_BLOCK, CLOSE_BLOCK)
927
+ private_constant :BLOCK_DELIMITER
928
+
779
929
  # @since 0.1.0
780
930
  # @api private
781
931
  attr_reader :adapter
@@ -812,6 +962,25 @@ module Dry
812
962
  raise MissingTargetError.new(target, path)
813
963
  end
814
964
 
965
+ # @since 0.3.0
966
+ # @api private
967
+ def closing_block_index(content, starting, path, target, delimiter, count_offset = 0) # rubocop:disable Metrics/ParameterLists
968
+ blocks_count = content.count { |line| line.match?(delimiter.opening) } + count_offset
969
+ matching_line = content.find do |line|
970
+ blocks_count -= 1 if line.match?(delimiter.closing)
971
+ line if blocks_count.zero?
972
+ end
973
+
974
+ (content.index(matching_line) or
975
+ raise MissingTargetError.new(target, path)) + starting
976
+ end
977
+
978
+ # @since 0.4.0
979
+ # @api private
980
+ def closing_class_index(content, starting, path, target, delimiter)
981
+ closing_block_index(content, starting, path, target, delimiter, 1)
982
+ end
983
+
815
984
  # @since 0.1.0
816
985
  # @api private
817
986
  def _inject_line_before(path, target, contents, finder)
@@ -839,6 +1008,8 @@ module Dry
839
1008
  if line.match?(NEW_LINE)
840
1009
  line = line.split(NEW_LINE)
841
1010
  _offset_block_lines(line, offset)
1011
+ elsif line.match?(EMPTY_LINE)
1012
+ line + NEW_LINE
842
1013
  else
843
1014
  offset + line + NEW_LINE
844
1015
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-files
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Guidi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-10 00:00:00.000000000 Z
11
+ date: 2022-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: rake
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '13.0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '13.0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: rspec
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -38,20 +24,6 @@ dependencies:
38
24
  - - "~>"
39
25
  - !ruby/object:Gem::Version
40
26
  version: '3.10'
41
- - !ruby/object:Gem::Dependency
42
- name: rubocop
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '1.12'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '1.12'
55
27
  description: file utilities
56
28
  email:
57
29
  - me@lucaguidi.com
@@ -77,7 +49,7 @@ licenses:
77
49
  - MIT
78
50
  metadata:
79
51
  allowed_push_host: https://rubygems.org
80
- changelog_uri: https://github.com/dry-rb/dry-files/blob/master/CHANGELOG.md
52
+ changelog_uri: https://github.com/dry-rb/dry-files/blob/main/CHANGELOG.md
81
53
  source_code_uri: https://github.com/dry-rb/dry-files
82
54
  bug_tracker_uri: https://github.com/dry-rb/dry-files/issues
83
55
  post_install_message: