dry-files 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b04750013e1faf98db463c2c346a5f43ed63f3dca9053a881082b02ac4c6e2a
4
- data.tar.gz: 1c3162ad5c735483ac4fbac02b588e111e55f92698682807dad68bf12503906a
3
+ metadata.gz: 62369326685ba2aa2719ff1ffd3bda010ac51f80e6f5094fde071c6ea440d407
4
+ data.tar.gz: 5f629c648d5d31f4d46bf7a32b80efc6a053e79990117978f9ec3e26a2a4490f
5
5
  SHA512:
6
- metadata.gz: e8a0a61aba226cc8f1f7dc01fb06a8c21e8c2405c131cae4e45689e551996fe2d72deae4b99b0e9e677aad294bdef1eca53183a75386344a89c136afd243784d
7
- data.tar.gz: d363a392c4427fe3b6b84349f5addcb839e7881bdcab9dcd7e6d32124632883ca490187d8e654b84ef0162a675cc25d2b928e9448504ef94132ad5076dc77110
6
+ metadata.gz: 4729286d5f15c84b98d8349104a2bd4b89106c6f9c100af996687c2968a7f9cb890fb2cca90821031ab5074172fb36d9fa0ffc15840caa31d4cbc60e4af71d2b
7
+ data.tar.gz: cbbda5c073e563b48145035ca9f1ed3ac7deceea3c02b78060225e8710d9bbd6cfee7727bb67fd254791689fdcb763608fc8cfaf1e4e31bcd168e96a9a5ad666
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
2
2
 
3
+ ## 0.3.0 2022-09-19
4
+
5
+
6
+ ### Added
7
+
8
+ - Support for `inject_line_at_class_bottom` (via #13) (@jodosha)
9
+
10
+
11
+ [Compare v0.2.0...v0.3.0](https://github.com/dry-rb/dry-files/compare/v0.2.0...v0.3.0)
12
+
3
13
  ## 0.2.0 2022-07-10
4
14
 
5
15
 
@@ -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 = "0.3.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,14 +1,14 @@
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: 0.3.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-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake