diff --git a/Gemfile.lock b/Gemfile.lock index 47da42a..464901e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,19 +9,46 @@ PATH GEM remote: https://rubygems.org/ specs: + ast (2.4.0) + coderay (1.1.2) curses (1.3.2) + jaro_winkler (1.5.4) + method_source (0.9.2) mini_portile2 (2.4.0) + minitest (5.13.0) nokogiri (1.10.7) mini_portile2 (~> 2.4.0) + parallel (1.19.1) + parser (2.7.0.0) + ast (~> 2.4.0) + pry (0.12.2) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + rainbow (3.0.0) rake (12.3.3) + rubocop (0.78.0) + jaro_winkler (~> 1.5.1) + parallel (~> 1.10) + parser (>= 2.6) + rainbow (>= 2.2.2, < 4.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 1.7) + rubocop-rspec (1.37.1) + rubocop (>= 0.68.1) + ruby-progressbar (1.10.1) thor (1.0.1) + unicode-display_width (1.6.0) PLATFORMS ruby DEPENDENCIES + minitest (~> 5.0) noozoid! + pry rake (~> 12.0) + rubocop + rubocop-rspec BUNDLED WITH 2.1.2 diff --git a/Rakefile b/Rakefile index 43022f7..289e643 100644 --- a/Rakefile +++ b/Rakefile @@ -1,2 +1,28 @@ require "bundler/gem_tasks" +require "rake/testtask" +require 'rubocop/rake_task' + +# Add additional test suite definitions to the default test task here +namespace :spec do + desc 'Runs RuboCop on specified directories' + RuboCop::RakeTask.new(:rubocop) do |task| + # Dirs: app, lib, test + task.patterns = ['exe/**/*.rb', 'lib/**/*.rb', 'spec/**/*_spec.rb'] + + # Make it easier to disable cops. + task.options << "--display-cop-names" + + # Abort on failures (fix your code first) + task.fail_on_error = true + end +end + +Rake::TestTask.new(:spec) do |t| + t.libs << "spec" + t.libs << "lib" + t.test_files = FileList['spec/**/*_spec.rb'] +end + +Rake::Task[:spec].enhance ['spec:rubocop'] + task :default => :spec diff --git a/exe/noozoid b/exe/noozoid index 1b9bf3a..6346281 100755 --- a/exe/noozoid +++ b/exe/noozoid @@ -7,103 +7,11 @@ # # Press `h` key when running for help. -require 'nokogiri' -require 'curses' +require 'noozoid/cli' -Curses.init_screen -Curses.curs_set(0) # invisible cursor +Noozoid::Cli.start(ARGV) - -begin - # Building a static window - win1 = Curses::Window.new(Curses.lines / 2 - 1, Curses.cols / 2 - 1, 0, 0) - win1.box("|", "-") - win1.setpos(2, 2) - win1.addstr("Hello") - win1.refresh - - # In this window, there will be an animation - win2 = Curses::Window.new(Curses.lines / 2 - 1, Curses.cols / 2 - 1, - Curses.lines / 2, Curses.cols / 2) - win2.box("|", "-") - win2.refresh - 2.upto(win2.maxx - 3) do |i| - win2.setpos(win2.maxy / 2, i) - win2 << "*" - win2.refresh - sleep 0.05 - end - - # Clearing windows each in turn - sleep 0.5 - win1.clear - win1.refresh - win1.close - sleep 0.5 - win2.clear - win2.refresh - win2.close - sleep 0.5 - -rescue - Curses.close_screen -end -exit 1 - -# Individual node type -class Node - - protected - - attr_accessor :parent - - public - - attr_accessor :name, :open - attr_reader :children, :parent - - def initialize(name = 'untitled') - @name = name - @children = [] - @parent = nil - @open = true - end - - def []=(child) - @children.push(child) - child.parent = self - end - - def [](i) - @children[i] - end - - def toggle! - @open = !@open - end - - def >>(n = 1) - return nil if @parent.nil? - - idx = @parent.children.index(self) - return nil if idx.nil? - - @parent[(idx + n) % @parent.children.length] - end - - def <<(n = 1) - self >> -n - end - - def remove - @parent.children.delete(self) unless @parent.nil? - end - - def children? - !@children.empty? - end - -end +exit 0 # Proper output of a tree module PrettyPrint @@ -119,19 +27,6 @@ module PrettyPrint end end -KEYS = { - nav_parent: 'h', - nav_child: 'l', - nav_next: 'j', - nav_previous: 'k', - nav_root: 'r', - node_create: 'a', - node_delete: 'd', - node_toggle: 'v', - main_quit: 'q', - main_help: '?' -} - def print_help puts '= Commands =' puts '' @@ -153,22 +48,6 @@ def print_help read_command end -def read_mm(file_path) - xml = Nokogiri::XML(IO.read(file_path)) - map_root = xml.xpath('/map/node')[0] - root = Node.new(map_root['TEXT']) - read_mm_subtree(root, map_root) - root -end - -def read_mm_subtree(node, xml) - xml.xpath('node').each do |xml_child| - node_child = Node.new(xml_child['TEXT']) - node[] = node_child - read_mm_subtree(node_child, xml_child) - end -end - def read_command system("stty raw -echo") #=> Raw mode, no echo char = STDIN.getc @@ -179,8 +58,6 @@ end if ARGV.empty? print 'Mindmap name: ' current = root = Node.new(STDIN.gets.chomp) -else - current = root = read_mm(ARGV[0]) end loop do diff --git a/exe/test.rb b/exe/test.rb new file mode 100644 index 0000000..7030b44 --- /dev/null +++ b/exe/test.rb @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby + +require 'curses' +include Curses + +file = File.open ARGV[0] + +begin + init_screen + file.each {|line| addstr(line) } + refresh + getch +ensure + close_screen +end diff --git a/lib/noozoid/actions/node_create.rb b/lib/noozoid/actions/node_create.rb new file mode 100644 index 0000000..e69de29 diff --git a/lib/noozoid/actions/node_delete.rb b/lib/noozoid/actions/node_delete.rb new file mode 100644 index 0000000..e69de29 diff --git a/lib/noozoid/actions/node_edit.rb b/lib/noozoid/actions/node_edit.rb new file mode 100644 index 0000000..e69de29 diff --git a/lib/noozoid/actions/node_move.rb b/lib/noozoid/actions/node_move.rb new file mode 100644 index 0000000..e69de29 diff --git a/lib/noozoid/actions/node_rename.rb b/lib/noozoid/actions/node_rename.rb new file mode 100644 index 0000000..e69de29 diff --git a/lib/noozoid/cli.rb b/lib/noozoid/cli.rb new file mode 100644 index 0000000..bb1ba30 --- /dev/null +++ b/lib/noozoid/cli.rb @@ -0,0 +1,17 @@ + +require 'thor' + +require_relative 'gui' + +module Noozoid + class Cli < Thor + + desc 'gui', 'Start ncurses GUI' + def gui + Gui.start + end + + default_task :gui + end +end + diff --git a/lib/noozoid/config.rb b/lib/noozoid/config.rb new file mode 100644 index 0000000..0014837 --- /dev/null +++ b/lib/noozoid/config.rb @@ -0,0 +1,30 @@ + +module Noozoid + module Config + HELP_KEYS = [ + [:nav_parent, 'Navigate to parent'], + [:nav_child, 'Navigate to child' ], + [:nav_next, 'Navigate to next sibling'], + [:nav_previous, 'Navigate to previous sibling'], + [:nav_root, 'Navigate to tree root'], + [:node_create, 'Create node'], + [:node_delete, 'Delete selected node'], + [:node_toggle, 'Toggle node'], + [:main_help, 'Show this help'], + [:main_quit, 'Exit program'] + ] + + DEFAULT_KEYS = { + nav_parent: 'h', + nav_child: 'l', + nav_next: 'j', + nav_previous: 'k', + nav_root: 'r', + node_create: 'a', + node_delete: 'd', + node_toggle: 'v', + main_quit: 'q', + main_help: '?' + } + end +end diff --git a/lib/noozoid/gui.rb b/lib/noozoid/gui.rb new file mode 100644 index 0000000..86fa345 --- /dev/null +++ b/lib/noozoid/gui.rb @@ -0,0 +1,55 @@ + +require 'curses' + +module Noozoid + module Gui + end +end + +require_relative 'widgets/help_widget' +require_relative 'widgets/main_widget' + +module Noozoid + module Gui + def start + Curses.init_screen + # invisible cursor + Curses.curs_set(0) + Curses.refresh + @main_window = MainWindow.new + while true do + @main_window.refresh + @main_window.loop + end + rescue Exception => e + Curses.close_screen + puts e + end + +=begin + win2 = Curses::Window.new(Curses.lines / 2 - 1, Curses.cols / 2 - 1, + Curses.lines / 2, Curses.cols / 2) + win2.box("|", "-") + win2.refresh + 2.upto(win2.maxx - 3) do |i| + win2.setpos(win2.maxy / 2, i) + win2 << "*" + win2.refresh + sleep 0.05 + end + + # Clearing windows each in turn + sleep 0.5 + win1.clear + win1.refresh + win1.close + sleep 0.5 + win2.clear + win2.refresh + win2.close + sleep 0.5 +=end + + module_function :start + end +end diff --git a/lib/noozoid/node.rb b/lib/noozoid/node.rb new file mode 100644 index 0000000..858aaf9 --- /dev/null +++ b/lib/noozoid/node.rb @@ -0,0 +1,57 @@ + +module Noozoid + # Individual node type + class Node + + protected + + attr_accessor :parent + + public + + attr_accessor :name, :open + attr_reader :children, :parent + + def initialize(name = 'untitled') + @name = name + @children = [] + @parent = nil + @open = true + end + + def []=(child) + @children.push(child) + child.parent = self + end + + def [](i) + @children[i] + end + + def toggle! + @open = !@open + end + + def >>(n = 1) + return nil if @parent.nil? + + idx = @parent.children.index(self) + return nil if idx.nil? + + @parent[(idx + n) % @parent.children.length] + end + + def <<(n = 1) + self >> -n + end + + def remove + @parent.children.delete(self) unless @parent.nil? + end + + def children? + !@children.empty? + end + + end +end diff --git a/lib/noozoid/widgets/help_widget.rb b/lib/noozoid/widgets/help_widget.rb new file mode 100644 index 0000000..f8b07c5 --- /dev/null +++ b/lib/noozoid/widgets/help_widget.rb @@ -0,0 +1,49 @@ + +require_relative '../config' + +module Noozoid + module Gui + class HelpWidget + def initialize + @win = Curses::Window.new( + Curses.lines / 2, + Curses.cols / 2, + Curses.lines / 4, + Curses.cols / 4 + ) + @win.box(?|, ?-) + @win.setpos(0, 1) + @win.addstr(' Noozoid help ') + + self.fill + @win.refresh + end + + def fill + t = 2 + Noozoid::Config::HELP_KEYS.each do |key, desc| + @win.setpos(t, 7) + @win.addstr(Noozoid::Config::DEFAULT_KEYS[key]) + @win.setpos(t, 10) + @win.addstr(desc) + + t += 1 + end + + str = 'Press q to close' + @win.setpos(@win.maxy - 2, @win.maxx - str.size - 2) + @win.addstr(str) + end + + def loop + while true + key = Curses.getch + break if key == 'q' + end + @win.clear + @win.refresh + @win.close + end + end + end +end diff --git a/lib/noozoid/widgets/main_widget.rb b/lib/noozoid/widgets/main_widget.rb new file mode 100644 index 0000000..b44fd4a --- /dev/null +++ b/lib/noozoid/widgets/main_widget.rb @@ -0,0 +1,49 @@ +require_relative '../version' + +module Noozoid::Gui + class MainWindow + def initialize + @win = Curses::Window.new(Curses.lines, Curses.cols, 0, 0) + # @win.box(?|, ?-) + # @win.refresh + @title = 'untitled' + end + + def title=(value) + @title = value + self.refresh + end + + def draw_header + top_str = "noozoid #{Noozoid::VERSION} ~ Use the arrow keys to navigate, press ? for help" + @win.attron(Curses::A_REVERSE) + @win.setpos(0, 0) + @win.addstr(" " * Curses.cols) + @win.setpos(0, 0) + @win.addstr(top_str) + @win.attroff(Curses::A_REVERSE) + @win.setpos(1, 0) + @win.addstr('-' * Curses.cols) + @win.setpos(1, 4) + @win.addstr(" #{@title} ") + end + + def refresh + self.draw_header + @win.refresh + end + + def loop + key = Curses.getch + @win.setpos(10, 1) + @win.addstr(" #{key.to_s} ") + case key + when ?? + subwin = HelpWidget.new + subwin.loop + when ?q + raise "Exit" + end + end + end +end diff --git a/noozoid.gemspec b/noozoid.gemspec index 5316fed..2149350 100644 --- a/noozoid.gemspec +++ b/noozoid.gemspec @@ -31,4 +31,10 @@ Gem::Specification.new do |spec| spec.add_dependency 'nokogiri', '~> 1.10' spec.add_dependency 'curses', '~> 1.3' spec.add_development_dependency 'rake', '~> 12.0' + + spec.add_development_dependency "minitest", "~> 5.0" + spec.add_development_dependency "pry" + spec.add_development_dependency "rubocop" + spec.add_development_dependency "rubocop-rspec" end + diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..cb6a3fd --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,9 @@ +$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) +require "namarara" + +require "minitest/autorun" +require 'pathname' + +def testfile(name) + Pathname.new(__FILE__).dirname.join('files', name) +end