ned 0.0.2 → 0.1.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: 160b7291a491361041677df083e0b86804f07b8cd8915c6bf0d488bf080caaa3
4
- data.tar.gz: 69e4cfa1448cf5e8b0f106d4e4782c498423aca84d3953b43618f6708f3fecf8
3
+ metadata.gz: ac2bb61f75a90fed9ba2ec389e0c3e7fe7e49e9405c4882f5f7f7116daff1c9e
4
+ data.tar.gz: 8d85fe67d13d2b655f31f40bcc6f67b4a61aa4204cdf1b0f0d1b7cda701084c1
5
5
  SHA512:
6
- metadata.gz: ec4eb7adade48c639e707e71ed1ef1cddf47415390af025990ec69530b4733bbd2989280ea6fdae1783efe37391f7a667c4eb7fcbf9775440a905a8a44e88455
7
- data.tar.gz: e898818ec2422271f845da80aca56389a637833e60e0247a96ed53569df00d66b93952332801b3a00778a110510b077d8fb183caf63fa3d2dee32a5e3f0d1761
6
+ metadata.gz: eb1802ef8d556fc19ceae4d93b1bf2ac2906d73386adee200b24043787bb7a29b07a1209fccfcf996d7472af84e83a418a819075c95ecae83ce9d40e6aecbf90
7
+ data.tar.gz: 3cc3810a8506518cb2e40bcc4d1dfda713cc21a0c46a05ba9057722639d5d6c76c559cd551f0e305b05d212eb3a712c1b08373f080b1df37b9c405ec87fe14e4
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ned (0.0.2)
4
+ ned (0.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -7,6 +7,7 @@ Usage: ned [[ned_arg ...] --] [[command] [: command ...]]
7
7
 
8
8
  OPTIONS
9
9
  -i, --inplace Edit each file in place by processing each file independently.
10
+ -s, --separate Process input files one at a time rather than as one stream.
10
11
  -h, --help [COMMAND] Print this help.
11
12
  -v, --version Print Ned version.
12
13
 
@@ -24,29 +25,55 @@ backward (b)
24
25
 
25
26
  Usage: ned backward
26
27
 
27
- head (h)
28
+ cut (c)
28
29
 
29
- Print only the first input lines.
30
+ Print selected portions of each line.
31
+
32
+ Usage: ned cut [--delimiter STRING | --pattern PATTERN] [--join STRING] --field (START|START..END) ...
33
+
34
+ Options:
35
+ -d, --delimiter STRING Separate input fields by the specified string. Defaults to space.
36
+ -p, --pattern PATTERN Separate input fields by the specified pattern instead of a delimiter.
37
+ -j, --join STRING Join output fields with the specified string. Defaults to space.
38
+ -f, --field INDEX_OR_RANGE Include the specified index or range in the output.
39
+
40
+ eval (e)
41
+
42
+ Evaluate the specified ruby string.
43
+
44
+ Usage: ned eval STRING
45
+
46
+ Variables:
47
+ l: the current line or lines
48
+ i: the current index (zero-based)
49
+
50
+ grep (g)
51
+
52
+ Print lines matching the specified pattern.
30
53
 
31
- Usage: ned head [--num NUM]
54
+ Usage: ned grep [--only] PATTERN
32
55
 
33
56
  Options:
34
- -n, --num NUM Number of input lines to print. Defaults to 10.
57
+ -o, --only Only print the matching portion of each line.
58
+ -v, --invert Print lines not matching the specified pattern.
59
+
60
+ head (h)
61
+
62
+ Print only the first input lines.
63
+
64
+ Usage: ned head [NUM]
35
65
 
36
66
  index (i)
37
67
 
38
- Print only the specified line(s). Use -1 etc to index from end.
68
+ Print only the lines indicated by the specified indices and ranges.
39
69
 
40
- Usage: ned index START [END]
70
+ Usage: ned index INDEX_OR_RANGE ...
41
71
 
42
72
  join (j)
43
73
 
44
74
  Join input lines, optionally with the specified delimiter.
45
75
 
46
- Usage: ned join [--delimiter STRING]
47
-
48
- Options:
49
- -d, --delimiter STRING Join using the specified delimiter.
76
+ Usage: ned join [STRING]
50
77
 
51
78
  prepend (p)
52
79
 
@@ -56,12 +83,9 @@ prepend (p)
56
83
 
57
84
  quote (q)
58
85
 
59
- Wrap each input line in quotes.
60
-
61
- Usage: ned quote [--quote STRING]
86
+ Wrap each input line in quotes, or the specified string.
62
87
 
63
- Options:
64
- -q, --quote STRING Quote using the specified string.
88
+ Usage: ned quote [STRING]
65
89
 
66
90
  sort (s)
67
91
 
@@ -77,10 +101,7 @@ tail (t)
77
101
 
78
102
  Print only the last input lines.
79
103
 
80
- Usage: ned tail [--num NUM]
81
-
82
- Options:
83
- -n, --num NUM Number of input lines to print. Defaults to 10.
104
+ Usage: ned tail [NUM]
84
105
 
85
106
  uniq (u)
86
107
 
data/lib/ned/command.rb CHANGED
@@ -28,6 +28,14 @@ module Ned
28
28
  config(:description, args)
29
29
  end
30
30
 
31
+ def require_all
32
+ @require_all = true
33
+ end
34
+
35
+ def require_all?
36
+ @require_all
37
+ end
38
+
31
39
  def option_parser(&block)
32
40
  if block_given?
33
41
  @option_parser = block
@@ -45,6 +53,8 @@ module Ned
45
53
 
46
54
  def initialize(input)
47
55
  @input = input
56
+ @require_all = true if self.class.require_all?
57
+ @first = true
48
58
  end
49
59
 
50
60
  def options
@@ -66,64 +76,56 @@ module Ned
66
76
  end
67
77
 
68
78
  def execute
69
- result = @input.execute
70
- return nil if result.nil?
79
+ if @require_all
80
+ load_lines.empty? ? nil : load_lines.shift
81
+ else
82
+ return nil if @next.nil? && !@first
71
83
 
72
- if result.is_a? Array
73
- if require_flat_map?
74
- result.flat_map do |line|
75
- execute_internal(line)
76
- end
84
+ if @first
85
+ @first = false
86
+ current = @input.execute
87
+ @next = @input.execute
77
88
  else
78
- result.map do |line|
79
- execute_internal(line)
89
+ current = @next
90
+ @next = @input.execute
91
+ end
92
+
93
+ if current.is_a? Array
94
+ current.flat_map do |line|
95
+ result = execute_internal(line)
96
+ result.is_a?(Array) ? result : [result]
80
97
  end
98
+ else
99
+ execute_internal(current)
81
100
  end
82
- else
83
- execute_internal(result)
84
101
  end
85
102
  end
86
103
 
87
104
  def execute_all
88
- if require_flat_map?
89
- lines = @input.execute_all.flat_map do |line|
90
- execute_internal(line)
91
- end
92
- lines
105
+ if @require_all
106
+ load_lines
107
+ @lines.shift(@lines.size)
93
108
  else
94
109
  lines = @input.execute_all
95
- lines.each do |line|
96
- execute_internal(line)
110
+ lines = lines.each_with_index.flat_map do |line, i|
111
+ @next = lines[i + 1]
112
+ result = execute_internal(line)
113
+ result.is_a?(Array) ? result : [result]
97
114
  end
98
115
  lines
99
116
  end
100
117
  end
101
118
 
102
- def require_flat_map(require_flat_map = true)
103
- @require_flat_map = require_flat_map
104
- end
105
-
106
- def require_flat_map?
107
- @require_flat_map || false
108
- end
109
-
110
119
  def execute_internal(line)
111
120
  line
112
121
  end
113
- end
114
122
 
115
- class AllCommand < Command
116
- def execute
117
- load_lines.empty? ? nil : load_lines.shift
118
- end
119
-
120
- def execute_all
121
- load_lines
122
- @lines.shift(@lines.size)
123
+ def require_all
124
+ @require_all = true
123
125
  end
124
126
 
125
- def ensure_trailing_newline
126
- @lines[-1].ensure_trailing_newline if @lines[-1]
127
+ def require_all?
128
+ @require_all
127
129
  end
128
130
 
129
131
  def load_lines
@@ -134,8 +136,8 @@ module Ned
134
136
  @lines
135
137
  end
136
138
 
137
- def execute_internal(lines)
138
- lines
139
+ def peek
140
+ require_all? ? @lines[0] : @next
139
141
  end
140
142
  end
141
143
  end
@@ -16,12 +16,12 @@ module Ned
16
16
 
17
17
  raise OptionParser::ParseError.new("missing string argument") if args.size == 0
18
18
  @append = args.shift
19
- require_flat_map if @append.index("\n")
19
+ @require_split = !!@append.index("\n")
20
20
  end
21
21
 
22
22
  def execute_internal(line)
23
23
  line.insert_before_newline(@append)
24
- require_flat_map? ? line.split(/(?<=\n)/) : line
24
+ @require_split ? line.split(/(?<=\n)/) : line
25
25
  end
26
26
 
27
27
  Ned::CommandRegistry.add(Append)
@@ -1,8 +1,10 @@
1
1
  module Ned
2
- class Backward < Ned::AllCommand
2
+ class Backward < Ned::Command
3
3
  long_name 'backward'
4
4
  short_name 'b'
5
5
 
6
+ require_all
7
+
6
8
  option_parser do |opts|
7
9
  opts.banner = <<~EOF
8
10
  Reverse input lines.
@@ -12,7 +14,7 @@ module Ned
12
14
  end
13
15
 
14
16
  def execute_internal(lines)
15
- ensure_trailing_newline
17
+ lines[-1].ensure_trailing_newline if lines[-1]
16
18
  lines.reverse!
17
19
  end
18
20
 
@@ -0,0 +1,81 @@
1
+ module Ned
2
+ class Cut < Ned::Command
3
+ long_name 'cut'
4
+ short_name 'c'
5
+
6
+ option_parser do |opts|
7
+ opts.banner = <<~EOF
8
+ Print selected portions of each line.
9
+
10
+ Usage: ned cut [--delimiter STRING | --pattern PATTERN] [--join STRING] --field (START|START..END) ...
11
+
12
+ Options:
13
+ EOF
14
+
15
+ opts.on('-d', '--delimiter STRING', 'Separate input fields by the specified string. Defaults to space.') do |delimiter|
16
+ raise OptionParser::ParseError.new('duplicate flag') if options[:delimiter]
17
+
18
+ delimiter
19
+ end
20
+
21
+ opts.on('-p', '--pattern PATTERN', 'Separate input fields by the specified pattern instead of a delimiter.') do |pattern|
22
+ raise OptionParser::ParseError.new('duplicate flag') if options[:pattern]
23
+
24
+ Regexp.new(pattern)
25
+ end
26
+
27
+ opts.on('-j', '--join STRING', 'Join output fields with the specified string. Defaults to space.') do |join|
28
+ raise OptionParser::ParseError.new('duplicate flag') if options[:join]
29
+
30
+ join
31
+ end
32
+
33
+ opts.on('-f', '--field INDEX_OR_RANGE', 'Include the specified index or range in the output.') do |index_or_range|
34
+ match = index_or_range.match(/(.*)[.][.](.*)/)
35
+ if match
36
+ start = Integer(match[1]) rescue nil
37
+ ending = Integer(match[2]) rescue nil
38
+ range = start..ending if start && ending
39
+ else
40
+ index = Integer(index_or_range) rescue nil
41
+ range = index..index if index
42
+ end
43
+
44
+ raise OptionParser::InvalidArgument.new(index_or_range) unless range
45
+
46
+ options[:ranges] ||= []
47
+ options[:ranges] << range
48
+ end
49
+ end
50
+
51
+ def parse(args)
52
+ super
53
+
54
+ raise OptionParser::ParseError.new('specify only one of --delimiter and --pattern') if options[:delimiter] && options[:pattern]
55
+ raise OptionParser::ParseError.new('you must specify one or more field indices or ranges') unless options[:ranges]
56
+
57
+ if options[:delimiter]
58
+ options[:join] ||= options[:delimiter]
59
+ else
60
+ options[:join] ||= ' '
61
+ end
62
+
63
+ @require_split = !!options[:join]&.index("\n")
64
+ end
65
+
66
+ def execute_internal(line)
67
+ ending = line[-1] == "\n" ? line.slice!(-1) : ''
68
+
69
+ columns = line.split(options[:pattern] || options[:delimiter])
70
+ result = options[:ranges].flat_map do |range|
71
+ columns[range]
72
+ end.join(options[:join])
73
+
74
+ result << ending
75
+
76
+ @require_split ? result.split(/(?<=\n)/) : result
77
+ end
78
+
79
+ Ned::CommandRegistry.add(Cut)
80
+ end
81
+ end
@@ -0,0 +1,57 @@
1
+ module Ned
2
+ class Eval < Command
3
+ long_name 'eval'
4
+ short_name 'e'
5
+
6
+ option_parser do |opts|
7
+ opts.banner = <<~EOF
8
+ Evaluate the specified ruby string.
9
+
10
+ Usage: ned eval STRING
11
+
12
+ Variables:
13
+ l: the current line or lines
14
+ i: the current index (zero-based)
15
+ EOF
16
+ end
17
+
18
+ def parse(args)
19
+ super
20
+
21
+ raise OptionParser::ParseError.new("missing string argument") if args.size == 0
22
+ @code = args.shift
23
+ end
24
+
25
+ def execute_internal(l)
26
+ @i ||= -1
27
+ @i += 1
28
+ i = @i
29
+
30
+ if l[-1] == "\n"
31
+ ending = "\n"
32
+ l.slice!(-1)
33
+ else
34
+ ending = ''
35
+ end
36
+
37
+ result = instance_eval(@code)
38
+ if result.is_a? Array
39
+ result = result.flat_map { |line| split_on_newline(line.to_s.ensure_trailing_newline) }
40
+ result[-1][-1] = ending if result[-1]
41
+ result
42
+ else
43
+ split_on_newline(result.to_s.insert(-1, ending))
44
+ end
45
+ end
46
+
47
+ def split_on_newline(line_or_lines)
48
+ if line_or_lines.is_a?(Array)
49
+ line_or_lines.flat_map { |line| line.to_s.split(/(?<=\n)/) }
50
+ else
51
+ line_or_lines.to_s.split(/(?<=\n)/)
52
+ end
53
+ end
54
+
55
+ Ned::CommandRegistry.add(Eval)
56
+ end
57
+ end
@@ -0,0 +1,50 @@
1
+ module Ned
2
+ class Grep < Ned::Command
3
+ long_name 'grep'
4
+ short_name 'g'
5
+
6
+ option_parser do |opts|
7
+ opts.banner = <<~EOF
8
+ Print lines matching the specified pattern.
9
+
10
+ Usage: ned grep [--only] PATTERN
11
+
12
+ Options:
13
+ EOF
14
+
15
+ opts.on('-o', '--only', 'Only print the matching portion of each line.') do |only|
16
+ raise OptionParser::ParseError.new('duplicate flag') if options[:only]
17
+
18
+ only
19
+ end
20
+
21
+ opts.on('-v', '--invert', 'Print lines not matching the specified pattern.') do |invert|
22
+ raise OptionParser::ParseError.new('duplicate flag') if options[:invert]
23
+
24
+ invert
25
+ end
26
+ end
27
+
28
+ def parse(args)
29
+ super
30
+
31
+ raise OptionParser::ParseError.new("missing pattern argument") if args.size == 0
32
+
33
+ @pattern = Regexp.new(args.shift)
34
+ end
35
+
36
+ def execute_internal(line)
37
+ line.slice!(-1) if line[-1] == "\n"
38
+
39
+ if options[:invert]
40
+ @pattern.match(line) ? [] : [line << "\n"]
41
+ elsif options[:only]
42
+ line.scan(@pattern).map(&:ensure_trailing_newline)
43
+ else
44
+ @pattern.match(line) ? [line << "\n"] : []
45
+ end
46
+ end
47
+
48
+ Ned::CommandRegistry.add(Grep)
49
+ end
50
+ end
@@ -7,24 +7,25 @@ module Ned
7
7
  opts.banner = <<~EOF
8
8
  Print only the first input lines.
9
9
 
10
- Usage: ned head [--num NUM]
11
-
12
- Options:
10
+ Usage: ned head [NUM]
13
11
  EOF
12
+ end
14
13
 
15
- opts.on('-n', '--num NUM', Integer, 'Number of input lines to print. Defaults to 10.') do |num|
16
- raise OptionParser::ParseError.new('duplicate flag') if options[:num]
17
-
18
- raise OptionParser::ParseError.new("invalid num: #{num}") if num < 0
14
+ def parse(args)
15
+ super
19
16
 
20
- num
17
+ if arg = args.shift
18
+ @num = Integer(arg) rescue nil
19
+ raise OptionParser::InvalidArgument.new(arg) unless @num
20
+ else
21
+ @num = 10
21
22
  end
22
23
  end
23
24
 
24
25
  def execute_internal(line)
25
26
  @index ||= 0
26
27
  @index += 1
27
- if @index <= options.fetch(:num, 10)
28
+ if @index <= @num
28
29
  line
29
30
  else
30
31
  nil
@@ -1,34 +1,52 @@
1
1
  module Ned
2
- class Index < AllCommand
2
+ class Index < Command
3
3
  long_name 'index'
4
4
  short_name 'i'
5
5
 
6
+ require_all
7
+
6
8
  option_parser do |opts|
7
9
  opts.banner = <<~EOF
8
- Print only the specified line(s). Use -1 etc to index from end.
10
+ Print only the lines indicated by the specified indices and ranges.
9
11
 
10
- Usage: ned index START [END]
12
+ Usage: ned index INDEX_OR_RANGE ...
11
13
  EOF
12
14
  end
13
15
 
14
16
  def parse(args)
15
- if index = args.index { |arg| Integer(arg) rescue false }
16
- @start = Integer(args.delete_at(index))
17
+ @ranges = []
18
+
19
+ i = 0
20
+ while arg = args[i]
21
+ range = nil
22
+ match = arg.match(/(.*)[.][.](.*)/)
23
+ if match
24
+ start = Integer(match[1]) rescue nil
25
+ ending = Integer(match[2]) rescue nil
26
+ range = start..ending if start && ending
27
+ else
28
+ index = Integer(arg) rescue nil
29
+ range = index..index if index
30
+ end
17
31
 
18
- if index = args.index { |arg| Integer(arg) rescue false }
19
- @end = Integer(args.delete_at(index))
32
+ if range
33
+ args.slice!(i)
34
+ @ranges << range
35
+ else
36
+ i += 1
20
37
  end
21
38
  end
22
39
 
23
- @end ||= @start
24
-
25
40
  super
26
41
 
27
- raise OptionParser::ParseError.new("missing index argument") unless @start
42
+ raise OptionParser::InvalidArgument.new(args[0]) unless args.empty?
43
+ raise OptionParser::ParseError.new("you must specify one or more indices or ranges") if @ranges.empty?
28
44
  end
29
45
 
30
46
  def execute_internal(lines)
31
- lines[@start..@end] || []
47
+ @ranges.flat_map do |range|
48
+ lines[range]
49
+ end
32
50
  end
33
51
 
34
52
  Ned::CommandRegistry.add(Index)
@@ -1,23 +1,23 @@
1
1
  module Ned
2
- class Join < Ned::AllCommand
2
+ class Join < Ned::Command
3
3
  long_name 'join'
4
4
  short_name 'j'
5
5
 
6
+ require_all
7
+
6
8
  option_parser do |opts|
7
9
  opts.banner = <<~EOF
8
10
  Join input lines, optionally with the specified delimiter.
9
11
 
10
- Usage: ned join [--delimiter STRING]
11
-
12
- Options:
12
+ Usage: ned join [STRING]
13
13
  EOF
14
+ end
14
15
 
15
- opts.on("-d", '--delimiter STRING', 'Join using the specified delimiter.') do |delimiter|
16
- raise OptionParser::ParseError.new('duplicate flag') if options[:delimiter]
16
+ def parse(args)
17
+ super
17
18
 
18
- @has_newline = delimiter.index("\n") != nil
19
- delimiter
20
- end
19
+ @delimiter = args.shift || ''
20
+ @has_newline = @delimiter.index("\n") != nil
21
21
  end
22
22
 
23
23
  def execute_internal(lines)
@@ -30,9 +30,9 @@ module Ned
30
30
  end
31
31
 
32
32
  if @has_newline
33
- lines.join(options.fetch(:delimiter, '')).insert(-1, trailing_newline).split(/(?<=\n)/)
33
+ lines.join(@delimiter).insert(-1, trailing_newline).split(/(?<=\n)/)
34
34
  else
35
- [lines.join(options.fetch(:delimiter, '')).insert(-1, trailing_newline)]
35
+ [lines.join(@delimiter).insert(-1, trailing_newline)]
36
36
  end
37
37
  end
38
38
 
@@ -16,12 +16,12 @@ module Ned
16
16
 
17
17
  raise OptionParser::ParseError.new("missing string argument") if args.size == 0
18
18
  @prepend = args.shift
19
- require_flat_map if @prepend.index("\n")
19
+ @require_split = !!@prepend.index("\n")
20
20
  end
21
21
 
22
22
  def execute_internal(line)
23
23
  line.insert(0, @prepend)
24
- require_flat_map? ? line.split(/(?<=\n)/) : line
24
+ @require_split ? line.split(/(?<=\n)/) : line
25
25
  end
26
26
 
27
27
  Ned::CommandRegistry.add(Prepend)
@@ -5,25 +5,23 @@ module Ned
5
5
 
6
6
  option_parser do |opts|
7
7
  opts.banner = <<~EOF
8
- Wrap each input line in quotes.
8
+ Wrap each input line in quotes, or the specified string.
9
9
 
10
- Usage: ned quote [--quote STRING]
11
-
12
- Options:
10
+ Usage: ned quote [STRING]
13
11
  EOF
12
+ end
14
13
 
15
- opts.on('-q', '--quote STRING', 'Quote using the specified string.') do |quote|
16
- raise OptionParser::ParseError.new('duplicate flag') if options[:quote]
14
+ def parse(args)
15
+ super
17
16
 
18
- require_flat_map if quote.index("\n")
19
- quote
20
- end
17
+ @quote = args.shift || '"'
18
+ @require_split = !!@quote.index("\n")
21
19
  end
22
20
 
23
21
  def execute_internal(line)
24
- line.insert(0, options.fetch(:quote, '"'))
25
- line.insert_before_newline(options.fetch(:quote, '"'))
26
- require_flat_map? ? line.split(/(?<=\n)/) : line
22
+ line.insert(0, @quote)
23
+ line.insert_before_newline(@quote)
24
+ @require_split ? line.split(/(?<=\n)/) : line
27
25
  end
28
26
 
29
27
  Ned::CommandRegistry.add(Quote)
@@ -31,7 +31,7 @@ module Ned
31
31
  lines.concat(current.readlines)
32
32
  current.close
33
33
  current = @inputs.shift
34
- lines[-1].ensure_trailing_newline if !current.nil? && lines[-1]
34
+ lines[-1].ensure_trailing_newline if !!current && lines[-1]
35
35
  end
36
36
 
37
37
  lines
@@ -1,10 +1,12 @@
1
1
  module Ned
2
- class Sort < Ned::AllCommand
2
+ class Sort < Ned::Command
3
3
  DIGIT_MATCH = /^ *((?:0|-?[1-9][0-9]*)(?:[.][0-9]+)?)(.*)/
4
4
 
5
5
  long_name 'sort'
6
6
  short_name 's'
7
7
 
8
+ require_all
9
+
8
10
  option_parser do |opts|
9
11
  opts.banner = <<~EOF
10
12
  Sort input lines.
@@ -28,7 +30,8 @@ module Ned
28
30
  end
29
31
 
30
32
  def execute_internal(lines)
31
- ensure_trailing_newline
33
+ lines[-1].ensure_trailing_newline if lines[-1]
34
+
32
35
  if options.fetch(:numeric, false)
33
36
  sortable = lines.map do |line|
34
37
  match = line.match(DIGIT_MATCH)
@@ -1,29 +1,31 @@
1
1
  module Ned
2
- class Tail < AllCommand
2
+ class Tail < Command
3
3
  long_name 'tail'
4
4
  short_name 't'
5
5
 
6
+ require_all
7
+
6
8
  option_parser do |opts|
7
9
  opts.banner = <<~EOF
8
10
  Print only the last input lines.
9
11
 
10
- Usage: ned tail [--num NUM]
11
-
12
- Options:
12
+ Usage: ned tail [NUM]
13
13
  EOF
14
+ end
14
15
 
15
- opts.on('-n', '--num NUM', Integer, 'Number of input lines to print. Defaults to 10.') do |num|
16
- raise OptionParser::ParseError.new('duplicate flag') if options[:num]
17
-
18
- raise OptionParser::ParseError.new("invalid num: #{num}") if num < 0
16
+ def parse(args)
17
+ super
19
18
 
20
- num
19
+ if arg = args.shift
20
+ @num = Integer(arg) rescue nil
21
+ raise OptionParser::InvalidArgument.new(arg) unless @num
22
+ else
23
+ @num = 10
21
24
  end
22
25
  end
23
26
 
24
27
  def execute_internal(lines)
25
- num = options.fetch(:num, 10)
26
- num < lines.length ? lines[-num..-1] : lines
28
+ @num < lines.length ? lines[-@num..-1] : lines
27
29
  end
28
30
 
29
31
  Ned::CommandRegistry.add(Tail)
@@ -1,5 +1,5 @@
1
1
  module Ned
2
- class Uniq < Ned::AllCommand
2
+ class Uniq < Ned::Command
3
3
  long_name 'uniq'
4
4
  short_name 'u'
5
5
 
@@ -19,34 +19,28 @@ module Ned
19
19
  end
20
20
  end
21
21
 
22
- def execute_internal(lines)
23
- ensure_trailing_newline
24
-
25
- previous = nil
26
- count = 1
27
- lines.each_with_index.flat_map do |line, i|
28
- result = []
29
- if previous && line != previous
30
- if options.fetch(:count, false)
31
- previous.insert(0, "#{count} ")
32
- result << previous
33
- else
34
- result << previous
35
- end
36
- count = 1
37
- elsif previous
38
- count += 1
39
- end
40
- previous = line
41
- if i == lines.length - 1
42
- if options.fetch(:count, false)
43
- line.insert(0, "#{count} ")
44
- result << line
45
- else
46
- result << line
47
- end
48
- end
49
- result
22
+ def parse(args)
23
+ super
24
+ @show_count = options.fetch(:count, false)
25
+ end
26
+
27
+ def execute_internal(line)
28
+ if peek.nil?
29
+ # last line
30
+ line.ensure_trailing_newline
31
+ else
32
+ # maybe last line, can't easily tell
33
+ peek.ensure_trailing_newline
34
+ end
35
+
36
+ @count = (@count || 0) + 1
37
+
38
+ if line != peek
39
+ line.insert(0, "#{@count} ") if @show_count
40
+ @count = 0
41
+ line
42
+ else
43
+ []
50
44
  end
51
45
  end
52
46
 
data/lib/ned/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ned
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/ned.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'optparse'
2
+ require 'tempfile'
2
3
 
3
4
  require 'ned/command'
4
5
  require 'ned/command_registry'
@@ -9,33 +10,13 @@ require 'ned/version'
9
10
  Dir["#{__dir__}/ned/commands/**/*.rb"].sort.each { |f| require f }
10
11
 
11
12
  # todo
12
- # - uniq shouldn't be an all command
13
- # - split into files
14
- # - commands shouldn't have to implement execute_all, put this in the base or use a wrapper or something
15
- # - implement help
16
- # - add --version
17
- # - write tests
18
- # - add tail
19
- # - add head
13
+ # - Add ability to check whether there are more lines.
20
14
  # - add replace
21
- # - add grep
22
- # - add grep -o or only
23
- # - add grep -v or except
24
- # - add index
25
- # - add join
26
15
  # - add count or something like wc
27
16
  # - implement inline editing
28
17
  # - implement highlighting
29
- # - implement append
30
- # - add ability for a command to turn one line into many
31
- # - add ability for a command to turn one line into zero
32
- # - handle newlines in quote, replace, etc.
33
- # - add numeric sort
34
- # - add reverse sort
35
18
  # - maybe add append and prepend
36
19
  # - figure out how to distrute as gem without bundle
37
- # - use OptionParser on main args.
38
- # - uniq should not need to be an AllCommand. Add ability to check whether there are more lines.
39
20
  # - add support for a ~/.ned/ dir
40
21
  # - implement highlight
41
22
  # - add color
@@ -47,6 +28,15 @@ Dir["#{__dir__}/ned/commands/**/*.rb"].sort.each { |f| require f }
47
28
  # - add support for to_set in uniq
48
29
  # - add support for overriding existing commands
49
30
  # - index shouldn't always be an all command. It should be based on the indices.
31
+ # - add a way to operate on input files separately, one at a time.
32
+ # - maybe add --all to eval to operate on all lines at once
33
+ # - write more tests:
34
+ # - '\:'
35
+ # - duplicate names
36
+ # - eval
37
+ # - no short name or no long name
38
+ # - maybe Command should provide an option to ensure the trailing newline is preserved rather than each command doing it alone.
39
+ # - add delete to delete strings
50
40
  module Ned
51
41
  class Main
52
42
  def initialize
@@ -67,6 +57,12 @@ module Ned
67
57
  inplace
68
58
  end
69
59
 
60
+ opts.on('-s', '--separate', 'Process input files one at a time rather than as one stream.') do |separate|
61
+ raise OptionParser::ParseError.new('duplicate flag') if @options[:separate]
62
+
63
+ separate
64
+ end
65
+
70
66
  opts.on('-h', '--help [COMMAND]', 'Print this help.') do |help|
71
67
  help || true
72
68
  end
@@ -139,14 +135,12 @@ module Ned
139
135
  return 0
140
136
  end
141
137
 
142
- raise 'in-place not implemented' if @options[:inplace]
143
-
144
138
  inputs = ned_args.map do |current|
145
139
  if current == '-'
146
140
  input
147
141
  else
148
142
  unless File.exist? current
149
- error.puts "fatal: file not found \"#{current}\""
143
+ error.puts "fatal: file not found: \"#{current}\""
150
144
  return 1
151
145
  end
152
146
  File.new(current)
@@ -192,19 +186,77 @@ module Ned
192
186
  return 1
193
187
  end
194
188
 
195
- if inputs.index(input) && @in_place
189
+ if @options[:inplace] && inputs.index(input)
196
190
  error.puts "fatal: option -i specified with stdin"
197
191
  return 1
198
192
  end
199
193
 
194
+ if @options[:inplace]
195
+ inputs.each do |i|
196
+ dup = command_args.map { |a| a.dup }
197
+ result, command = create_command(dup, [i], output, error)
198
+
199
+ return result if result
200
+
201
+ if inputs.index(input) && !has_stdin
202
+ error.puts 'fatal: can\'t read from stdin'
203
+ return 1
204
+ end
205
+
206
+ temp = Tempfile.new(File.expand_path(i))
207
+ print = Ned::Print.new(command, temp)
208
+
209
+ while line = print.execute; end
210
+
211
+ temp.rewind
212
+ IO.copy_stream(temp, File.expand_path(i))
213
+ temp.unlink
214
+ end
215
+ elsif @options[:separate]
216
+ inputs.each do |i|
217
+ dup = command_args.map { |a| a.dup }
218
+ result, command = create_command(dup, [i], output, error)
219
+
220
+ return result if result
221
+
222
+ if inputs.index(input) && !has_stdin
223
+ error.puts 'fatal: can\'t read from stdin'
224
+ return 1
225
+ end
226
+
227
+ print = Ned::Print.new(command, output)
228
+
229
+ while line = print.execute; end
230
+ end
231
+ else
232
+ result, command = create_command(command_args, inputs, output, error)
233
+
234
+ return result if result
235
+
236
+ if inputs.index(input) && !has_stdin
237
+ error.puts 'fatal: can\'t read from stdin'
238
+ return 1
239
+ end
240
+
241
+ print = Ned::Print.new(command, output)
242
+
243
+ while line = print.execute; end
244
+ end
245
+
246
+ 0
247
+ end
248
+
249
+ def create_command(command_args, inputs, output, error)
200
250
  previous = Ned::Read.new(inputs)
201
251
  command_args.each do |args|
202
252
  command = Ned::CommandRegistry.find(args.shift).new(previous)
203
253
  begin
204
254
  command.parse(args)
205
- raise OptionParser::ParseError.new("invalid arguments: #{args}") unless args.empty?
255
+ raise OptionParser::InvalidArgument.new(args[0]) unless args.empty?
206
256
  rescue OptionParser::ParseError => e
207
257
  error.puts "fatal: error while parsing options for #{command.class.long_name}: #{e.message}"
258
+ error.puts
259
+ error.puts command.class.help
208
260
  return 1
209
261
  end
210
262
 
@@ -221,16 +273,7 @@ module Ned
221
273
  previous = command
222
274
  end
223
275
 
224
- if inputs.index(input) && !has_stdin
225
- error.puts 'fatal: can\'t read from stdin'
226
- return 1
227
- end
228
-
229
- print = Ned::Print.new(previous, output)
230
-
231
- while line = print.execute; end
232
-
233
- 0
276
+ [nil, previous]
234
277
  end
235
278
  end
236
279
  end
data/scripts/release CHANGED
@@ -12,6 +12,8 @@ if [ -n "$(git status --porcelain)" ]; then
12
12
  exit 1;
13
13
  fi
14
14
 
15
+ git pull
16
+
15
17
  NEW_VERSION=$1
16
18
  CURRENT_VERSION=$(grep VERSION lib/ned/version.rb | cut -d'"' -f 2)
17
19
 
@@ -28,4 +30,5 @@ gem build
28
30
  gem push ned-$NEW_VERSION.gem
29
31
  bundle install
30
32
  git commit -a -m "v$NEW_VERSION Release"
33
+ git push
31
34
  open "https://github.com/nicholasdower/ned/releases/new?title=v$NEW_VERSION%20Release&tag=v$NEW_VERSION&target=$(git rev-parse HEAD)"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ned
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Dower
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-07 00:00:00.000000000 Z
11
+ date: 2022-06-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A stream editor
14
14
  email:
@@ -28,6 +28,9 @@ files:
28
28
  - lib/ned/command_registry.rb
29
29
  - lib/ned/commands/append.rb
30
30
  - lib/ned/commands/backward.rb
31
+ - lib/ned/commands/cut.rb
32
+ - lib/ned/commands/eval.rb
33
+ - lib/ned/commands/grep.rb
31
34
  - lib/ned/commands/head.rb
32
35
  - lib/ned/commands/index.rb
33
36
  - lib/ned/commands/join.rb
@@ -50,7 +53,6 @@ files:
50
53
  - scripts/generate_readme
51
54
  - scripts/release
52
55
  - scripts/test
53
- - spec_old/file_spec.rb
54
56
  homepage: https://github.com/nicholasdower/ned
55
57
  licenses:
56
58
  - MIT
@@ -1,29 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe 'files' do
4
- test_failure 'no input', args: '-s', stdin: nil, stderr: 'fatal: no input'
5
-
6
- test_failure 'no such file', args: 'foo -s', stdin: nil, stderr: 'fatal: no such file: foo'
7
-
8
- test_failure 'multiple files', args: 'spec/support/file spec/support/file -s', stdin: nil, stderr: 'fatal: more than one input not yet supported'
9
-
10
- test_failure 'no input, standard in as file arg', args: '- -s', stdin: nil, stderr: 'fatal: no input'
11
-
12
- test_failure 'standard in as file arg multiple times', args: '- - -s', stdin: '', stderr: "fatal: can't read from standard in twice, silly"
13
-
14
- context 'all lines read' do
15
- test_success 'standard in', stdin: "two\none\n", args: '-s', stdout: "one\ntwo\n"
16
-
17
- test_success 'standard in as file arg', stdin: "two\none\n", args: '- -s', stdout: "one\ntwo\n"
18
-
19
- test_success 'single file', stdin: nil, args: 'spec/support/file -s', stdout: "one\nthree\ntwo\n"
20
- end
21
-
22
- context 'lines read one at a time' do
23
- test_success 'standard in', stdin: "two\none\n", args: '-a 1', stdout: "two1\none1\n"
24
-
25
- test_success 'standard in as file arg', stdin: "two\none\n", args: '- -a 1', stdout: "two1\none1\n"
26
-
27
- test_success 'single file', stdin: nil, args: 'spec/support/file -a 1', stdout: "one1\ntwo1\nthree1\n"
28
- end
29
- end