treely 1.0.7 → 1.0.8

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: baa98bb08c38105f10e629aa7e34356746b27402ae29ad7c05d459d281457166
4
- data.tar.gz: 627b9afaf8b6d6a12e35c82098f3a814a7313a3f659508000fc7036f9f334e96
3
+ metadata.gz: 653fb44ea3510d808f5682762303cdaae5ff8c72ce45e6039a7967b9a3710abc
4
+ data.tar.gz: cd439a0dfdef8d5945f1108fa859c0a2219d362fbe8cef2dd9daace3df549b2d
5
5
  SHA512:
6
- metadata.gz: 031f4612e1eae8d823182008c7be10de46e69c8e16d291e00313eb780970e5b86f81429f5d72f8deb2baae2fdbda635275ae2fa94770761c91bc3f44e94430cd
7
- data.tar.gz: dbf8587b48b5d28d4d908e0956671ab70b6c399c564da5b5b375103f9a07ff357372d8104b764e38f013804e80dc7f62af773272224e6b89396d1f18e66f9505
6
+ metadata.gz: ba25e2181283352c1b5f8072446cfe46b4248a0d015583dd101aeaa273f5242dd07de4541837df1b61690ad2bf9a91ac28325887d7fe2381d5e53e272cac7d17
7
+ data.tar.gz: 5ee904b64314c1c3d976cb5d05d8675e799487977fbac1513393738d223e880d758c02a17c526115c395c54e8422d5ed38389e036d8be48a854976b048bbe13b
data/bin/treely CHANGED
@@ -1,37 +1,48 @@
1
1
  #!/usr/bin/env ruby
2
- require 'json'
3
- require 'pathname'
4
-
2
+ require 'optparse'
3
+ require 'ostruct'
5
4
  require 'treely'
6
5
  require 'treely/directory'
7
6
 
8
- require 'active_support/core_ext/object/blank'
9
- require 'active_support/core_ext/string/inflections'
10
- require 'mercenary'
7
+ options = OpenStruct.new(
8
+ :dirs_first => false,
9
+ :files_first => false,
10
+ :only_dirs => false,
11
+ :show_hidden => false,
12
+ :no_report => false,
13
+ :ignore_p => [],
14
+ :level => -1
15
+ )
16
+
17
+ parser = OptionParser.new do |op|
18
+ op.banner = 'Usage: treely [options] [PATH]'
19
+ op.on('-d', '--only-dirs', 'List only directories') { options.only_dirs = true }
20
+ op.on('-A', '--show-hidden', 'List hidden (dot) entries') { options.show_hidden = true }
21
+ op.on('-D', '--dirs-first', 'Sort directories before files') { options.dirs_first = true }
22
+ op.on('-F', '--files-first', 'Sort files before directories') { options.files_first = true }
23
+ op.on('-I', '--ignore PATTERN', 'Exclude entries matching PATTERN') { options.ignore_p << Regexp.new(_1) }
24
+ op.on('-L', '--level N', Integer, 'Descend at most N levels') { options.level = _1 }
25
+ op.on('-s', '--style NAME', 'Style: built-in UNICODE') do
26
+ Treely.configuration.style = _1.downcase.to_sym
27
+ end
11
28
 
12
- Mercenary.program(:treely) do |program|
13
- program.version Treely::VERSION
14
- program.syntax 'treely .'
29
+ op.on('-h', '--help', 'Show this help message') do
30
+ puts op
31
+ exit
32
+ end
33
+ end
15
34
 
16
- program.action do |args, options|
17
- contents = if args[0].present?
18
- Treely::Directory.new(args[0]).contents
19
- else
20
- JSON.parse(ARGF.read, symbolize_names: true)
21
- end
35
+ begin
36
+ parser.parse!
22
37
 
23
- if contents.any? { |item| item.has_key?(:files) }
24
- puts Treely.tree(contents[0..-2])
25
- puts %{}
26
- puts '%s %s, %s %s' % [
27
- contents[-1].fetch(:directories),
28
- %{directories}.pluralize(contents[-1][:directories]),
29
- contents[-1].fetch(:files),
30
- %{files}.pluralize(contents[-1][:files])
31
- ]
32
- else
33
- puts Treely.tree(contents)
34
- puts %{}
35
- end
38
+ if options.dirs_first
39
+ options.files_first = false
36
40
  end
41
+
42
+ Treely.dir(ARGV.shift, options).to_tree
43
+ .tap { |tree| tree.render.each { puts _1 } }
44
+ .tap { |tree| tree.walk_dir.summary }
45
+ rescue OptionParser::ParseError => err
46
+ $stderr.puts "Error: #{err.message}"
47
+ exit 1
37
48
  end
@@ -1,21 +1,11 @@
1
1
  module Treely
2
2
  class Configuration
3
- attr_reader :style
3
+ attr_accessor :style
4
+ attr_accessor :filter
5
+ attr_accessor :formatter
4
6
 
5
7
  def initialize
6
- @style = {
7
- :dot => %{\x2e},
8
- :space => %{\x20},
9
- :new_line => %{\x0a},
10
- :bar => %{│ },
11
- :indent => %{ },
12
- :branch => %{├── },
13
- :last_branch => %{└── }
14
- }
15
- end
16
-
17
- def transform_style!(&block)
18
- @style.transform_values!(&block)
8
+ @style = :unicode
19
9
  end
20
10
  end
21
11
 
@@ -23,10 +13,6 @@ module Treely
23
13
  @configuration ||= Configuration.new
24
14
  end
25
15
 
26
- def self.configuration=(config)
27
- @configuration = config
28
- end
29
-
30
16
  def self.configure
31
17
  yield configuration
32
18
  end
@@ -1,39 +1,120 @@
1
1
  module Treely
2
2
  class Directory
3
- def initialize(path)
4
- @pathname = Pathname(path)
5
- @directories, @files = [], []
6
- @ignore_pattern = /\A\.(\.?|\w+)\z/
3
+ extend Forwardable
7
4
 
8
- unless @pathname.directory?
9
- @contents = []
5
+ attr_reader :root
6
+ attr_reader :dirs_count
7
+ attr_reader :files_count
8
+
9
+ def initialize(root, options = {})
10
+ @root = root || Dir.pwd
11
+ @dirs_first = options[:dirs_first]
12
+ @files_first = options[:files_first]
13
+ @only_dirs = options[:only_dirs]
14
+ @show_hidden = options[:show_hidden]
15
+ @no_report = options[:no_report]
16
+ @ignore_p = options[:ignore_p]
17
+ @dirs_count = 0
18
+ @files_count = 0
19
+ @file_limit = -1
20
+ @filters = []
21
+ @level = options[:level].to_i
22
+
23
+ add_filter(-> { directory?(_1) }) if @only_dirs
24
+ add_filter(-> { !hidden?(_1) }) unless @show_hidden
25
+
26
+ Array(@ignore_p).each do |re|
27
+ add_filter(-> (p) { !p.match?(re) })
10
28
  end
11
29
  end
12
30
 
13
- def contents
14
- @contents ||= digger.call(@pathname)
15
- [*@contents, { directories: @directories.size, files: @files.size }]
31
+ def to_tree
32
+ tree = Treely.dir_tree
33
+ tree.walk_dir = self
34
+ tree
16
35
  end
17
36
 
18
- private
37
+ def walk(root, level = 0, maybe_last = true)
38
+ Enumerator.new do |emit|
39
+ emit << [basename(root), level, maybe_last]
40
+
41
+ if directory?(root) && descend?(level)
42
+ paths = Dir.children(root)
43
+ .sort.map { join(root, _1) }
44
+ .select { |p| @filters.all? { |f| f.call(p) } }
45
+
46
+ if @dirs_first or @files_first
47
+ paths.sort_by! {
48
+ is_dir = directory?(_1)
49
+ @dirs_first ? [is_dir ? 0 : 1, _1] : [is_dir ? 1 : 0, _1]
50
+ }
51
+ end
52
+
53
+ unless @no_report
54
+ dirs, files = paths.partition { directory?(_1) }
55
+ @dirs_count += dirs.size
56
+ @files_count += files.size
57
+ end
19
58
 
20
- def digger
21
- lambda do |pathname|
22
- pathname.children.reject { |n| @ignore_pattern.match?(n.basename.to_s) }.map do |pathname|
23
- if pathname.directory?
24
- @directories << pathname
25
- { name: pathname.basename.to_s, contents: digger.call(pathname) }
26
- else
27
- @files << pathname
28
- { name: pathname.basename.to_s }
59
+ paths.each_with_index do |path, i|
60
+ walk(path, level + 1, i.next == paths.length)
61
+ .each { |t| emit << t }
29
62
  end
30
- end.sort_by do |item|
31
- [
32
- item[:contents].class.to_s,
33
- item[:name]
34
- ]
35
63
  end
36
64
  end
37
65
  end
66
+
67
+ def summary
68
+ return if @no_report
69
+ puts "\n%s %s, %s %s" % [
70
+ @dirs_count, @dirs_count == 1 ? 'directory' : 'directories',
71
+ @files_count, @files_count == 1 ? 'file' : 'files'
72
+ ]
73
+ end
74
+
75
+ private
76
+
77
+ def_delegator :File, :directory?
78
+ def_delegator :File, :basename
79
+ def_delegator :File, :join
80
+
81
+ def add_filter(filter)
82
+ @filters << filter
83
+ end
84
+
85
+ def descend?(level)
86
+ @level <= 0 || level < @level
87
+ end
88
+
89
+ def hidden?(path)
90
+ basename(path).start_with?('.')
91
+ end
92
+
93
+ module Source
94
+ def self.included(base)
95
+ base.attr_reader :walk_dir
96
+ end
97
+
98
+ def walk_dir=(dir)
99
+ @walk_dir = dir
100
+ @buffer = Tree::Buffer.new(source)
101
+ end
102
+
103
+ def source
104
+ @source ||= @walk_dir.walk(root)
105
+ end
106
+
107
+ def root
108
+ @root ||= @walk_dir.root
109
+ end
110
+ end
111
+ end
112
+
113
+ if defined?(Tree)
114
+ Tree.include(Directory::Source)
115
+ end
116
+
117
+ def self.dir(*args)
118
+ Directory.new(*args)
38
119
  end
39
120
  end
@@ -0,0 +1,22 @@
1
+ module Treely
2
+ class Tree
3
+ class Buffer
4
+ def initialize(source)
5
+ @iterator = source.each
6
+ @buffer = []
7
+ end
8
+
9
+ def [](i)
10
+ while @buffer.size <= i
11
+ begin
12
+ @buffer << @iterator.next
13
+ rescue StopIteration
14
+ break
15
+ end
16
+ end
17
+
18
+ @buffer[i]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,40 @@
1
+ module Treely
2
+ class Tree
3
+ module Style
4
+ UNICODE = {
5
+ :indent => ' ',
6
+ :bar => '│ ',
7
+ :branch => '├── ',
8
+ :last_branch => '└── '
9
+ }
10
+
11
+ UNICODE_ROUNDED = {
12
+ :indent => ' ',
13
+ :bar => '│ ',
14
+ :branch => '├── ',
15
+ :last_branch => '╰── '
16
+ }
17
+
18
+ UNICODE_TRIANGLE = {
19
+ :indent => ' ',
20
+ :bar => '┆ ',
21
+ :branch => '▸ ',
22
+ :last_branch => '▸ '
23
+ }
24
+
25
+ STYLES = {
26
+ :unicode => UNICODE,
27
+ :unicode_rounded => UNICODE_ROUNDED,
28
+ :unicode_triangle => UNICODE_TRIANGLE
29
+ }
30
+
31
+ def self.get_or_default(style)
32
+ STYLES[style] or UNICODE
33
+ end
34
+
35
+ def self.get(style)
36
+ STYLES[style]
37
+ end
38
+ end
39
+ end
40
+ end
data/lib/treely/tree.rb CHANGED
@@ -1,31 +1,127 @@
1
+ require 'treely/tree/buffer'
2
+ require 'treely/tree/style'
3
+
1
4
  module Treely
2
5
  class Tree
3
- def initialize(source)
4
- @style = Treely[:style]
5
- @contents = %{}
6
- treely source
6
+ attr_reader :style
7
+ attr_reader :filter
8
+ attr_reader :formatter
9
+
10
+ def initialize(elems = [], config: Configuration.new)
11
+ @style = Style.get_or_default(config.style)
12
+ @buffer = Buffer.new(walk(elems))
13
+
14
+ @formatter = -> { _1.to_s }
15
+ @filter = -> { _1 }
16
+
17
+ @formatter = config.formatter if config.formatter.is_a?(Proc)
18
+ @filter = config.filter if config.filter.is_a?(Proc)
7
19
  end
8
20
 
9
- def to_s
10
- @contents
21
+ def render
22
+ depth = 0
23
+ i = 0
24
+
25
+ Enumerator.new do |emit|
26
+ was_last = false
27
+ indents = []
28
+
29
+ loop do
30
+ current = @buffer[i]
31
+ break if current.nil?
32
+
33
+ elem, cur_depth, maybe_last = current
34
+ last_branch = if maybe_last
35
+ has_sibling = false
36
+ j = i + 1
37
+
38
+ loop do
39
+ t = @buffer[j]
40
+ break if t.nil?
41
+ break if t[1] < cur_depth
42
+
43
+ if t[1] == cur_depth
44
+ has_sibling = true
45
+ break
46
+ end
47
+
48
+ j += 1
49
+ end
50
+
51
+ !has_sibling
52
+ else
53
+ false
54
+ end
55
+
56
+ if cur_depth > depth
57
+ if was_last
58
+ indents << @style[:indent]
59
+ else
60
+ indents << @style[:bar]
61
+ end
62
+ elsif cur_depth < depth
63
+ indents = indents.take(cur_depth)
64
+ end
65
+
66
+ emit << render_str(elem, indents.join, last_branch)
67
+ was_last = last_branch
68
+ depth = cur_depth
69
+ i += 1
70
+ end
71
+ end
72
+ end
73
+
74
+ def render_str(elem, indent, last_branch, fn = @formatter)
75
+ lines = fn.call(elem).lines(chomp: true)
76
+ lines = [''] if lines.empty?
77
+
78
+ branch = last_branch ? @style[:last_branch] : @style[:branch]
79
+ bar = last_branch ? @style[:indent] : @style[:bar]
80
+
81
+ lines.map.with_index do |line, i|
82
+ indent + (i.zero? ? branch : bar) + line
83
+ end.join("\n")
11
84
  end
12
85
 
13
86
  private
14
87
 
15
- def treely(source, *args)
16
- source.each.with_index do |item, index|
17
- @contents << args.map { |name| @style[name] }.join
18
- @contents << @style[index.zero? ? :dot : :bar]
19
- @contents << @style[:new_line]
20
- @contents << args.map { |name| @style[name] }.join
21
- @contents << @style[source[index.next] ? :branch : :last_branch]
22
- @contents << item.fetch(:name)
23
- @contents << @style[:new_line]
24
-
25
- if item.has_key?(:contents)
26
- treely item[:contents], *args, (source[index.next] ? :bar : :indent)
88
+ def walk(elems, level = 0, maybe_last = true)
89
+ last_leaf, end_marks = level_hints(elems)
90
+
91
+ Enumerator.new do |emit|
92
+ elems.each_with_index do |elem, i|
93
+ if container?(elem)
94
+ walk(elem, level + 1, end_marks.include?(i))
95
+ .each { |t| emit << t }
96
+ else
97
+ emit << [
98
+ elem,
99
+ level,
100
+ maybe_last && last_leaf == i
101
+ ]
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ def level_hints(elems)
108
+ end_leaf = elems.rindex { !container?(_1) } || -1
109
+ end_marks = Set.new
110
+
111
+ elems.each_with_index do |elem, i|
112
+ cur_is_cont = container?(elem)
113
+ next_is_cont = container?(elems[i + 1])
114
+
115
+ if cur_is_cont && !next_is_cont
116
+ end_marks << i
27
117
  end
28
118
  end
119
+
120
+ [end_leaf, end_marks]
121
+ end
122
+
123
+ def container?(elem)
124
+ elem.is_a?(Array)
29
125
  end
30
126
  end
31
127
  end
@@ -1,3 +1,3 @@
1
1
  module Treely
2
- VERSION = '1.0.7'
2
+ VERSION = '1.0.8'
3
3
  end
data/lib/treely.rb CHANGED
@@ -1,15 +1,15 @@
1
+ require 'forwardable'
2
+ require 'set'
1
3
  require 'treely/configuration'
2
- require 'treely/version'
3
4
  require 'treely/tree'
5
+ require 'treely/version'
4
6
 
5
7
  module Treely
6
- module_function
7
-
8
- def tree(source)
9
- Tree.new(source)
8
+ def self.dir_tree
9
+ Tree.new(config: configuration)
10
10
  end
11
11
 
12
- def [](name)
13
- configuration.send(name)
12
+ def self.tree(elems)
13
+ Tree.new(elems).render.to_a.join("\n")
14
14
  end
15
15
  end
metadata CHANGED
@@ -1,45 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: treely
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.7
4
+ version: 1.0.8
5
5
  platform: ruby
6
6
  authors:
7
- - razeos at tossdev
7
+ - oneureka
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-12 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: activesupport
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '6.0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '6.0'
27
- - !ruby/object:Gem::Dependency
28
- name: mercenary
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: 0.4.0
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 0.4.0
11
+ date: 2026-10-06 00:00:00.000000000 Z
12
+ dependencies: []
41
13
  description:
42
- email:
14
+ email: oneureka@github.io
43
15
  executables:
44
16
  - treely
45
17
  extensions: []
@@ -50,10 +22,12 @@ files:
50
22
  - lib/treely/configuration.rb
51
23
  - lib/treely/directory.rb
52
24
  - lib/treely/tree.rb
25
+ - lib/treely/tree/buffer.rb
26
+ - lib/treely/tree/style.rb
53
27
  - lib/treely/version.rb
54
- homepage: https://github.com/tualand/treely
28
+ homepage: https://dub.sh/treely
55
29
  licenses:
56
- - MIT
30
+ - BSD-1-Clause
57
31
  metadata: {}
58
32
  post_install_message:
59
33
  rdoc_options: []
@@ -63,15 +37,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
63
37
  requirements:
64
38
  - - ">="
65
39
  - !ruby/object:Gem::Version
66
- version: '0'
40
+ version: '3.0'
67
41
  required_rubygems_version: !ruby/object:Gem::Requirement
68
42
  requirements:
69
43
  - - ">="
70
44
  - !ruby/object:Gem::Version
71
45
  version: '0'
72
46
  requirements: []
73
- rubygems_version: 3.1.2
47
+ rubygems_version: 3.5.22
74
48
  signing_key:
75
49
  specification_version: 4
76
- summary: A gem for generating tree-like format
50
+ summary: A gem for generating tree-like formats
77
51
  test_files: []