treely 1.0.6 → 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: 3ada2893182b188a97d04a1844edbb4dab5265be041acc9cebbc6fa1c27a94d9
4
- data.tar.gz: e7eda4c56eb35c0e23089f52ccf3005abfb57b2c67b1597a97c4390a232b1e57
3
+ metadata.gz: 653fb44ea3510d808f5682762303cdaae5ff8c72ce45e6039a7967b9a3710abc
4
+ data.tar.gz: cd439a0dfdef8d5945f1108fa859c0a2219d362fbe8cef2dd9daace3df549b2d
5
5
  SHA512:
6
- metadata.gz: f54267ed8875380568156048cff035f281c5b635a9f9bfd64dfc783b621dfee1c3c9a5fe5e8973910cb94863447ae80466fad78b7a439608b7d28033495eccf5
7
- data.tar.gz: 611dec22b0e4cd79db1987658dc59ec4131e2a1ef0fc57fedf71befd5d3d5b5b1c665ad204428e5f428d5e4f415b1cbe246e88721790cbf1264b8e0163f2f937
6
+ metadata.gz: ba25e2181283352c1b5f8072446cfe46b4248a0d015583dd101aeaa273f5242dd07de4541837df1b61690ad2bf9a91ac28325887d7fe2381d5e53e272cac7d17
7
+ data.tar.gz: 5ee904b64314c1c3d976cb5d05d8675e799487977fbac1513393738d223e880d758c02a17c526115c395c54e8422d5ed38389e036d8be48a854976b048bbe13b
data/bin/treely CHANGED
@@ -1,38 +1,48 @@
1
1
  #!/usr/bin/env ruby
2
-
3
- require 'json'
4
- require 'pathname'
5
-
2
+ require 'optparse'
3
+ require 'ostruct'
6
4
  require 'treely'
7
5
  require 'treely/directory'
8
6
 
9
- require 'active_support/core_ext/object/blank'
10
- require 'active_support/core_ext/string/inflections'
11
- 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
+ )
12
16
 
13
- Mercenary.program(:treely) do |program|
14
- program.version Treely::VERSION
15
- program.syntax 'treely .'
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
16
28
 
17
- program.action do |args, options|
18
- contents = if args[0].present?
19
- Treely::Directory.new(args[0]).contents
20
- else
21
- JSON.parse(ARGF.read, symbolize_names: true)
22
- end
29
+ op.on('-h', '--help', 'Show this help message') do
30
+ puts op
31
+ exit
32
+ end
33
+ end
34
+
35
+ begin
36
+ parser.parse!
23
37
 
24
- if contents.any? { |item| item.has_key?(:files) }
25
- puts Treely.tree(contents[0..-2])
26
- puts %{}
27
- puts "%s %s, %s %s" % [
28
- contents[-1].fetch(:directories),
29
- %{directories}.pluralize(contents[-1][:directories]),
30
- contents[-1].fetch(:files),
31
- %{files}.pluralize(contents[-1][:files])
32
- ]
33
- else
34
- puts Treely.tree(contents)
35
- puts %{}
36
- end
38
+ if options.dirs_first
39
+ options.files_first = false
37
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
38
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
@@ -0,0 +1,3 @@
1
+ module Treely
2
+ VERSION = '1.0.8'
3
+ end
data/lib/treely.rb CHANGED
@@ -1,16 +1,15 @@
1
+ require 'forwardable'
2
+ require 'set'
1
3
  require 'treely/configuration'
2
4
  require 'treely/tree'
5
+ require 'treely/version'
3
6
 
4
7
  module Treely
5
- module_function
6
-
7
- def tree(source)
8
- Tree.new(source)
8
+ def self.dir_tree
9
+ Tree.new(config: configuration)
9
10
  end
10
11
 
11
- def [](name)
12
- configuration.send(name)
12
+ def self.tree(elems)
13
+ Tree.new(elems).render.to_a.join("\n")
13
14
  end
14
-
15
- self.const_set 'version'.upcase, '1.0.6'
16
15
  end
metadata CHANGED
@@ -1,59 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: treely
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 1.0.8
5
5
  platform: ruby
6
6
  authors:
7
- - matark
7
+ - oneureka
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-22 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: minitest
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '5.11'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '5.11'
27
- - !ruby/object:Gem::Dependency
28
- name: activesupport
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '5.2'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '5.2'
41
- - !ruby/object:Gem::Dependency
42
- name: mercenary
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: 0.3.6
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: 0.3.6
11
+ date: 2026-10-06 00:00:00.000000000 Z
12
+ dependencies: []
55
13
  description:
56
- email: matark@naver.com
14
+ email: oneureka@github.io
57
15
  executables:
58
16
  - treely
59
17
  extensions: []
@@ -64,9 +22,12 @@ files:
64
22
  - lib/treely/configuration.rb
65
23
  - lib/treely/directory.rb
66
24
  - lib/treely/tree.rb
67
- homepage: https://opensource.matark.de/treely
25
+ - lib/treely/tree/buffer.rb
26
+ - lib/treely/tree/style.rb
27
+ - lib/treely/version.rb
28
+ homepage: https://dub.sh/treely
68
29
  licenses:
69
- - MIT
30
+ - BSD-1-Clause
70
31
  metadata: {}
71
32
  post_install_message:
72
33
  rdoc_options: []
@@ -76,15 +37,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
76
37
  requirements:
77
38
  - - ">="
78
39
  - !ruby/object:Gem::Version
79
- version: '0'
40
+ version: '3.0'
80
41
  required_rubygems_version: !ruby/object:Gem::Requirement
81
42
  requirements:
82
43
  - - ">="
83
44
  - !ruby/object:Gem::Version
84
45
  version: '0'
85
46
  requirements: []
86
- rubygems_version: 3.0.3
47
+ rubygems_version: 3.5.22
87
48
  signing_key:
88
49
  specification_version: 4
89
- summary: A Ruby library for generating tree-like format
50
+ summary: A gem for generating tree-like formats
90
51
  test_files: []