Prepare project structure
This commit is contained in:
commit
e9a7d7a815
10 changed files with 354 additions and 0 deletions
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/.bundle/
|
||||||
|
/.yardoc
|
||||||
|
/_yardoc/
|
||||||
|
/coverage/
|
||||||
|
/doc/
|
||||||
|
/pkg/
|
||||||
|
/spec/reports/
|
||||||
|
/tmp/
|
18
Gemfile
Normal file
18
Gemfile
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
||||||
|
|
||||||
|
# gem "rails"
|
||||||
|
|
||||||
|
gem "nokogiri", "~> 1.10"
|
||||||
|
|
||||||
|
|
||||||
|
gem "curses", "~> 1.3"
|
||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
# Specify your gem's dependencies in noozoid.gemspec
|
||||||
|
gemspec
|
||||||
|
|
||||||
|
gem "rake", "~> 12.0"
|
17
Gemfile.lock
Normal file
17
Gemfile.lock
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
curses (1.3.2)
|
||||||
|
mini_portile2 (2.4.0)
|
||||||
|
nokogiri (1.10.7)
|
||||||
|
mini_portile2 (~> 2.4.0)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
ruby
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
curses (~> 1.3)
|
||||||
|
nokogiri (~> 1.10)
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
2.1.2
|
35
README.md
Normal file
35
README.md
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# Noozoid
|
||||||
|
|
||||||
|
A terminal-based mindmap editor for geeks.
|
||||||
|
|
||||||
|
The name of the project comes from the Greek words _νους_ (mind) and _ζούδι_ (small animal).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Type the following line in your favorite terminal:
|
||||||
|
|
||||||
|
$ gem install noozoid
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
TODO: Write usage instructions here
|
||||||
|
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
||||||
|
|
||||||
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
||||||
|
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Bug reports and pull requests are welcome on GitHub at https://github.com/glenux/noozoid.
|
||||||
|
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
* <https://stac47.github.io/ruby/curses/tutorial/2014/01/21/ruby-and-curses-tutorial.html>
|
||||||
|
|
||||||
|
|
||||||
|
|
2
Rakefile
Normal file
2
Rakefile
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
require "bundler/gem_tasks"
|
||||||
|
task :default => :spec
|
218
exe/noozoid
Executable file
218
exe/noozoid
Executable file
|
@ -0,0 +1,218 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
# Usage:
|
||||||
|
# - Start new mindmap:
|
||||||
|
# `ruby ttymindmap.rb`
|
||||||
|
# - Open Freemind format MM mindmap:
|
||||||
|
# `ruby ttymindmap.rb <PATH-TO-MM-FILE>`
|
||||||
|
#
|
||||||
|
# Press `h` key when running for help.
|
||||||
|
|
||||||
|
require 'nokogiri'
|
||||||
|
require 'curses'
|
||||||
|
|
||||||
|
Curses.init_screen
|
||||||
|
Curses.curs_set(0) # invisible cursor
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Proper output of a tree
|
||||||
|
module PrettyPrint
|
||||||
|
def self.tree(subtree, current, indent = 0)
|
||||||
|
print ' ' * indent
|
||||||
|
print subtree == current ? '> ' : '- '
|
||||||
|
print subtree.name + "\n"
|
||||||
|
|
||||||
|
return unless subtree.open
|
||||||
|
subtree.children.each do |child|
|
||||||
|
tree(child, current, indent + 2)
|
||||||
|
end
|
||||||
|
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 ''
|
||||||
|
puts '- Navigation -'
|
||||||
|
puts "#{KEYS[:nav_parent]}: go to parent node"
|
||||||
|
puts "#{KEYS[:nav_child]}: go to children node"
|
||||||
|
puts "#{KEYS[:nav_previous]}: previous sibling"
|
||||||
|
puts "#{KEYS[:nav_next]}: next sibling"
|
||||||
|
puts ''
|
||||||
|
puts "- Action -"
|
||||||
|
puts "#{KEYS[:node_create]}: create child node"
|
||||||
|
puts "#{KEYS[:node_delete]}: remove node"
|
||||||
|
puts "#{KEYS[:node_toggle]}: toggle"
|
||||||
|
puts ''
|
||||||
|
puts "- Misc -"
|
||||||
|
puts "#{KEYS[:main_help]}: show this help"
|
||||||
|
puts "#{KEYS[:main_quit]}: exit program"
|
||||||
|
puts '[press a key to continue]'
|
||||||
|
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
|
||||||
|
system("stty -raw echo") #=> Reset terminal mode
|
||||||
|
char
|
||||||
|
end
|
||||||
|
|
||||||
|
if ARGV.empty?
|
||||||
|
print 'Mindmap name: '
|
||||||
|
current = root = Node.new(STDIN.gets.chomp)
|
||||||
|
else
|
||||||
|
current = root = read_mm(ARGV[0])
|
||||||
|
end
|
||||||
|
|
||||||
|
loop do
|
||||||
|
print `clear`
|
||||||
|
PrettyPrint.tree(root, current)
|
||||||
|
|
||||||
|
cmd = read_command
|
||||||
|
|
||||||
|
if cmd == KEYS[:node_create]
|
||||||
|
print 'Title: '
|
||||||
|
current[] = Node.new(STDIN.gets.chomp)
|
||||||
|
elsif cmd == KEYS[:node_delete]
|
||||||
|
current.remove
|
||||||
|
current = current.parent unless current.parent.nil?
|
||||||
|
elsif cmd == KEYS[:node_toggle]
|
||||||
|
current.toggle!
|
||||||
|
elsif cmd == KEYS[:nav_child]
|
||||||
|
current = current[0] if current.children?
|
||||||
|
elsif cmd == KEYS[:nav_parent]
|
||||||
|
current = current.parent unless current.parent.nil?
|
||||||
|
elsif cmd == KEYS[:nav_previous]
|
||||||
|
sibling = current >> -1
|
||||||
|
current = sibling unless sibling.nil?
|
||||||
|
elsif cmd == KEYS[:nav_next]
|
||||||
|
sibling = current >> 1
|
||||||
|
current = sibling unless sibling.nil?
|
||||||
|
elsif cmd == KEYS[:nav_root]
|
||||||
|
current = root
|
||||||
|
elsif cmd == KEYS[:main_help]
|
||||||
|
print_help
|
||||||
|
elsif cmd == KEYS[:main_quit]
|
||||||
|
puts 'Good Bye!'
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
6
lib/noozoid.rb
Normal file
6
lib/noozoid.rb
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
require "noozoid/version"
|
||||||
|
|
||||||
|
module Noozoid
|
||||||
|
class Error < StandardError; end
|
||||||
|
# Your code goes here...
|
||||||
|
end
|
3
lib/noozoid/version.rb
Normal file
3
lib/noozoid/version.rb
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module Noozoid
|
||||||
|
VERSION = "0.1.0"
|
||||||
|
end
|
28
noozoid.gemspec
Normal file
28
noozoid.gemspec
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
require_relative 'lib/noozoid/version'
|
||||||
|
|
||||||
|
Gem::Specification.new do |spec|
|
||||||
|
spec.name = "noozoid"
|
||||||
|
spec.version = Noozoid::VERSION
|
||||||
|
spec.authors = ["Glenn Y. Rolland"]
|
||||||
|
spec.email = ["glenux@glenux.net"]
|
||||||
|
|
||||||
|
spec.summary = %q{TODO: Write a short summary, because RubyGems requires one.}
|
||||||
|
spec.description = %q{TODO: Write a longer description or delete this line.}
|
||||||
|
spec.homepage = "TODO: Put your gem's website or public repo URL here."
|
||||||
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
||||||
|
|
||||||
|
spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
||||||
|
|
||||||
|
spec.metadata["homepage_uri"] = spec.homepage
|
||||||
|
spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
||||||
|
spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
||||||
|
|
||||||
|
# Specify which files should be added to the gem when it is released.
|
||||||
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
||||||
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
||||||
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
||||||
|
end
|
||||||
|
spec.bindir = "exe"
|
||||||
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||||
|
spec.require_paths = ["lib"]
|
||||||
|
end
|
19
shard.yml
Normal file
19
shard.yml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
name: noozoid
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
# authors:
|
||||||
|
# - name <email@example.com>
|
||||||
|
|
||||||
|
# description: |
|
||||||
|
# Short description of ttymindmap
|
||||||
|
|
||||||
|
# dependencies:
|
||||||
|
# pg:
|
||||||
|
# github: will/crystal-pg
|
||||||
|
# version: "~> 0.5"
|
||||||
|
|
||||||
|
# development_dependencies:
|
||||||
|
# webmock:
|
||||||
|
# github: manastech/webmock.cr
|
||||||
|
|
||||||
|
# license: MIT
|
Loading…
Reference in a new issue