diff --git a/2022/ruby/day_07.rb b/2022/ruby/day_07.rb
new file mode 100644
index 0000000..a52c19e
--- /dev/null
+++ b/2022/ruby/day_07.rb
@@ -0,0 +1,69 @@
+input = ARGF.read.lines(chomp: true).slice_when { _2.start_with?(?$) }
+
+Dir = Struct.new(:parent, :name, :children) do
+ include Enumerable
+
+ def size = children.sum(&:size)
+
+ def each(&block)
+ return enum_for(__method__, &block) unless block
+
+ block.(self)
+
+ children.each do |child|
+ case child
+ when Dir
+ child.each(&block)
+ when File
+ block.(child)
+ else
+ fail child.inspect
+ end
+ end
+ end
+end
+
+File = Struct.new(:parent, :name, :size)
+
+root = Dir.new(nil, ?/, [])
+pwd = root
+
+input.each do |chunk|
+ cmd, *out = *chunk
+ case cmd
+ when /\$ cd \//
+ # no-op
+ when /\$ cd \.\./
+ pwd = pwd.parent
+ when /\$ cd (.+)/
+ pwd = pwd.children.find { _1.name == $1 }
+ when /\$ ls/
+ out.each do |line|
+ case line
+ when /(\d+) (.+)/
+ pwd.children << File.new(pwd, $2, $1.to_i)
+ when /dir (.+)/
+ pwd.children << Dir.new(pwd, $1, [])
+ else
+ fail line
+ end
+ end
+ else
+ fail chunk.inspect
+ end
+end
+
+# part 1
+p root.each
+ .select { Dir === _1 }
+ .map(&:size)
+ .select { _1 < 100_000 }
+ .sum
+
+# part 2
+p root.each
+ .select { Dir === _1 }
+ .map(&:size)
+ .sort
+ .find { _1 >= 30_000_000 - (70_000_000 - root.size) }
+