Difference between revisions of "FreeplaneJrubyInstaller - developers"

From Freeplane - free mind mapping and knowledge management software
Line 2: Line 2:
 
This wiki page is intended for new programmers that want to try Ruby, and make Freeplane ruby scripts.
 
This wiki page is intended for new programmers that want to try Ruby, and make Freeplane ruby scripts.
  
If you just want to ''run a ruby script'', then instead read [[FreeplaneJrubyInstaller_Addon.md|FreeplaneJrubyInstaller Addon]]  
+
If you just want to '''''run a ruby script''''', then instead read [[FreeplaneJrubyInstaller_Addon.md|FreeplaneJrubyInstaller Addon]]  
 
   
 
   
  

Revision as of 07:39, 9 September 2016

This wiki page is intended for new programmers that want to try Ruby, and make Freeplane ruby scripts.

If you just want to run a ruby script, then instead read FreeplaneJrubyInstaller Addon


I know only a small number of people will actually feel curious to learn ruby and make freeplane scripts with ruby, which makes us - the freeplane-ruby-scripts programmers - a really small and nice club :)
So, if you anytime feel part-of-the-club (or just want to say hello), dont be shy and drop a comment in this forum thread: XXXXXXXTODOXXXXX - Hope we all have a great time :)


So here we go: learn ruby, program freeplane, have fun :)


Lets play first and then latter read the instructions

  • In Freeplane, click Tools/Jruby.../Ruby Live Debugger.../Debug in paralell Thread (GUI responsive), and wait (2min) until a new black window appears
  • Now follow one of these video examples:

TODO



Freeplane ruby script = Ruby + Freeplane Scripting Api

A ruby script for freeplane, is a ruby program that will use Freeplane java-objects from the Scripting API


To make ruby scripts for freeplane, the ideal learning-path is to:

  1. Learn the basics of Ruby
  2. Know how to call the Freeplane-java-objects in your ruby script. This is in fact 2 things:
    1. Learn what freeplane-java-objects are available for you - the Freeplane Scripting API
    2. Learn how to call freeplane-java-objects in (J)ruby


In the next sections, we'll go through this learning-path, which I trimmed down only to the essentials (with links for further exploration, if/when you need)



Learn the basics of Ruby

Dont be afraid, ruby is a simple language, easy to learn for new programmers, with lots of books, guides, videos and explanations in internet (almost too much)

HeadsUp: Stay away from "Ruby on Rails" because that is another universe geared towards using ruby with special rails magic to make webpages. There is a lot of learning material about Ruby On Rails on the internet, but you should stay away from it, as we just want to learn Ruby (without Rails magic), and even then, just the basics of Ruby. So just ignore all you find in internet about Ruby on Rails, it does not apply to freeplane.


So I recommend you read and follow these learning links:

  • Try ruby in 15 minutes, in the browser
    • >> I think this should be your first contact with ruby: a guided interactive tour to do simple things in ruby
    • Its all online, no install needed, and seems like 15-30min
    • From here take a general idea of ruby - you'll see these things explained in more detail in other guides/books
  • Online Interactive Ruby (online irb)
    • >> Keep this close to you everytime you are learning ruby, it allows you to try ruby code without installing anything. This is the equivalent of an online irb.
    • Instead of installing ruby in your pc and then use its "irb" to test ruby code, you can use this online irb without installing anything :)
    • Good to try out small code snippets, like examples from a book, or for you to explore ruby


Also, the following links are optional support-material - they will not teach you but you should have them somewhere close, as they will help you many times.

They contain more than info than you will ever need, but are good consulting references, to clearout doubts, see examples, etc. I still use them myself:

  • Ruby QuickRef Card (print it)
    • >> Print these 2 pages in 1 sheet and keep it around in the desk - it will help you many times to remember ruby syntax. Take notes on it as you need :)
  • Zenspider Ruby QuickRef (bookmark it)
    • >> This is the best quickref of ruby. I always search here first. Its too big to be printed (what a pitty) but whenever you want, you can open it online, and "find" what you want
    • Its a great reference to be consulted when needed! I never got even close to know all of it, so don't sweat. Just a reference, use it as you need it :)
  • Search in google "TutorialsPoint Ruby Basics" - TutorialsPoint Ruby Basics (consult when needed)
    • >> Use it like support-material: when you have a doubt about something specific, search for it in here
    • It packs good info in short resumes. Sometimes a bit canonical, but the examples can be of use for you and clarifying
    • Use it to consult, like a reference with examples and resumes.
    • This wiki filters the webpage link as spam (which is not), so use google to reach it


For freeplane scripts, its enough to learn only the basics of ruby - you decide what is your confort zone, and whenever you want/need to learn a bit more.

NOTE: If someday you want to become a ruby pro, you will want to read the "pickaxe" book: The Pragmatic Programmers' Guide, Programming Ruby If you are a new programmer, dont start here.


Know how to call the Freeplane-java-objects in your ruby script

Learn what freeplane-java-objects are available for you - the Freeplane Scripting API

Now, we will skim over the Freeplane Scripting API, which are a group of Java classes and its methods.

All the Freeplane Scripting API are Java classes and methods.

This will not make much sense to you now, after having learned the basics of ruby, because you don't know how to use Java classes/methods in ruby. These java classes will not be used just now, I will just present them so that you have an idea that they exist, and where you can find them latter, when you need them.


The freeplane-java-objects are documented in the Freeplane Scripting API, which is a Java API, where you can see the classes , the methods of each class and a short description of what each method does. The Freeplane Scripting Api should be open by your side while your making freeplane scripts - you will consult it all the time. Click the "FRAMES" link at the top, to show in the webpage a left-side-navigation-list with all the Classes available.


For starters, the most interesting class of the Freeplane Scripting API, is Proxy.Node

  • Represented in groovy and ruby scripts by the variable node
  • For starters, take note of these java methods: getText() and setText("whatever")


In Ruby scripts (imitating what happens with Groovy scripts), there are some handy special variables predefined for us to use in scripts, that point to usefull java-classes:


Also lets put here some usefull RUBY code snippets using the API, that will come in handy:

   # Show message in new window
   ui.informationMessage("Hello World!")
   
   # Show message in status bar (bottom of freeplane) 
   c.statusInfo = "Hello World again!"



Learn how to call freeplane-java-objects in (J)ruby

In last section I presented the Freeplane Scripting API, which are a group of Java classes/methods. In this section, I will focus on how to use Java classes/methods inside a (j)ruby script.

There will be some examples, that you can try yourself in the RubyLiveDebugger (Tools / Jruby / Ruby Live Debugger / Debug in EDT thread (GUI freezed) Also, have the Proxy.Node webpage nearby.


Read this section once, and take a pause. Then re-read it again. It's normal to don't understand-it-all in the first time (and second), at least that's what happened to me.
So know in advance that this will not be all understood in one pass - so read it relaxedly and let it sink-in afterwards. Then the next morning, re-read it.




The ruby language cannot use java-objects by default, but the Jruby-complete.jar adds some special-jruby-magic that makes possible for the ruby programs executed by the jruby-complete.jar to use java-objects.

So Jruby = ruby + <some-special-magic-that-enables-us-to-call-java-objects-from-inside-jruby-scripts>.

That jruby <special-magic> is refered as "Driving Java from Ruby", and I will resume here the basics of it that are enough for freeplane-ruby-script. For more info, see links at the end of section


In a normal Ruby program (not Jruby, just Ruby as you saw in Learn the basics of Ruby ), there is:

  • ruby code that drives/uses
    • Ruby-objects (Ruby-classes/methods)


In a Jruby program (not Ruby, but Jruby now), there is:

  • ruby code that drives/uses
    • Ruby-objects (Ruby-classes/methods)
    • Java-objects (Java classes/methods)



Access Scripting API Java-classes: node, c, ui, logger, htmlUtils, textUtils, menuUtils, config

In Jruby, you can access any Java-class, however we will focus just on the Java objects from the Freeplane Scripting API.
We have special variables predefined for us, to access the most-usefull Scripting classes: node, c, ui, logger, htmlUtils, textUtils, menuUtils, config (see Learn what freeplane-java-objects are available for you - the Freeplane Scripting API). So you can simply use node to access the Proxy.Node Java-class, and the other special-variables to access other Scripting java-classes that are usefull.


I recommend that to start you only use the node (and maybe others special variables predefined). The node will allow you to read/write into a node text, details, attributes, ... so should give more than enough runway for a smooth start into scripting :) For now, stay away from the other Java-classes of the Scripting API - they are all usefull in its own way, but not the best to start with scripting.



NOTE: Ignore the following if you are starting with scripting

If/when you need to access any other Java-classes, you can do it like this:

   # Access Java-classes of Scripting API that are  -->> Proxy.Xyzzz <<-- with:
   Java::org.freeplane.plugin.script.proxy.XyzzzProxy    # Java: Proxy.Xyzzz  (notice that XyzzzProxy <---- Proxy.Xyzzz )
   Java::org.freeplane.plugin.script.proxy.MapProxy      # Java: Proxy.Map
   Java::org.freeplane.plugin.script.proxy.NodeProxy     # Java: Proxy.Node
       #
       # NOTE: In technicall rigor, I believe that the Java-Interface 'Proxy.Node' has a Java-Class 'NodeProxy',
       # and that Java-Class is used in Jruby as 'NodeProxy'
       # I believe that is why all the Java-Interfaces (all the Proxy.Xyzzz) are accessed with 'XyzzzProxy'
       #
   
   # Access Java-classes of Scripting API that are not -->> Proxy.Xxxxx <<-- with:
   Java::org.freeplane.core.util.FreeplaneVersion        # Java: FreeplaneVersion
   Java::org.freeplane.core.util.HtmlUtils               # Java: HtmlUtils
   Java::org.freeplane.core.util.LogUtils                # Java: LogUtils



Call Java-methods: getXXX(), setXXX(yyy), isXXX(), ...

In Jruby, you can call Java-methods in multiple ways - they all do the same:

   # Calling java-methods in "java-style"
   the_node_text = node.getText()
   node.setText("hello!")
   bool_value = node.isRoot()
   node.createChild("Im a newborne child node")
   
   # In ruby the parenthesis are optional
   # So we could write it as:
   the_node_text = node.getText
   node.setText "hello!"  
   bool_value = node.isRoot
   node.createChild "Im a newborne child node" 
   
   # Jruby also allows to call aJavaMethod() like this: a_java_method()
   # So we could write it as:
   the_node_text = node.get_text
   node.set_text "hello!"
   bool_value = node.is_Root
   node.create_child("Im a newborne child node")
   
   # Jruby also allows to call these java-methods getXXX(), setXXX(yyy), isXXX() like this:  
   #
   #            a_value = obj.getXXX()         can be written as as        a_value = obj.XXX 
   #            obj.setXXX(yyy)                can be written as as        obj.XXX = yyy
   #            a_bool = obj.isXXX()           can be written as as         a_bool = obj.XXX?
   #
   # (for more info see https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#alternative-names-and-beans-convention)
   # So we could write it as:
   the_node_text = node.text
   node.text = "hello!"   
   bool_value = node.root?
   node.createChild("Im a newborne child node")
   
   

It's up to you to decide which to use.
En easy start is using the node.getText() ("java-style") that is closer to the Scripting API syntax.
With time, you may get more confortable with node.text and node.root?



Call Java-methods: conversions between ruby-type <--> java-type (automatic and manual)

Each Java-class has methods. Each Java method takes 0-or-more arguments, and returns a return-value When Jruby calls a Java-method, it automatically:

These "conversions" between ruby-object and java-object are automatic and seamless - they happen behind the scenes, invisible to you (the programmer). Jruby does that on purpose: it takes care of it as much as possible to avoid having you think about it.


The "conversions" are automatic for some most-used ruby-types and java-types. However, for other ruby-types and java-types the conversion can be made manually by the programmer (you)


The automatic conversions (made seamlessly by Jruby) between most used ruby-types <-> java-types, are:

FreeplaneJrubyInstaller 160908 170934.png


The manual conversions (that you as programmer may use) for some (commonly used) ruby-types <-> java-types, are:

  • java.util.List --> ruby-Array
   # java.util.List --> ruby-Array: use .to_a
   a_java_array_list = java.util.ArrayList.new([1,2,3])  # or Vector, LinkedList, Collection, ...
   a_ruby_array = a_java_array_list.to_a
   #=> [1, 2, 3]
  • java.util.Map --> ruby-Hash
   # java.util.Map --> ruby-Hash: use .to_hash
   a_java_hash_map = java.util.HashMap.new(:a => 123) # or Hashtable
   a_ruby_hash = a_java_hash_map.to_hash
   #=> {:a = 123}
  • ruby-Time --> java.util.Date
   # ruby-Time --> java.util.Date: use .to_java
   a_ruby_time = Time.now
   a_java_date = a_ruby_time.to_java
   #=> #<Java::JavaUtil::Date:0x747541f8>



Some examples of conversions:

   # NodeProxy.setNoteText() expects as argument a java.lang.String object
   # but we can give as argument a ruby-String, and Jruby will automatically convert the ruby-String into a java.lang.String
   # see http://freeplane.sourceforge.net/doc/api/org/freeplane/plugin/script/proxy/Proxy.Node.html#setNoteText-java.lang.String-
   node.setNoteText("Hi Im a ruby-string")
   # NodeProxy.getNoteText() returns a java.lang.String object, which Jruby automatically converts
   # into a ruby-String
   # see http://freeplane.sourceforge.net/doc/api/org/freeplane/plugin/script/proxy/Proxy.NodeRO.html#getNoteText--
   a_ruby_string = node.getNoteText()
   # NodeProxy.getChildren() returns a java.util.List<Proxy.Node>
   # Jruby does not automatically convert a java.util.List, but you can convert it into a ruby-Array
   # see http://freeplane.sourceforge.net/doc/api/org/freeplane/plugin/script/proxy/Proxy.NodeRO.html#getChildren--
   a_java_list = node.getChildren()
   a_ruby_array = a_java_list.to_a
   



Jruby tips:

   # In Jruby to access a Java-class-field, use :: like this SomeJavaClass::SomeJavaField
   # Access FreeplaneVersion::XML_VERSION (see http://freeplane.sourceforge.net/doc/api/org/freeplane/core/util/FreeplaneVersion.html)
   the_java_class = Java::org.freeplane.core.util.FreeplaneVersion
   the_value_of_field = the_java_class::XML_VERSION


   # To call a java-class-constructor, just:
   a_java_instance = JavaClass.new()
   a_java_instance = JavaClass.new(x,y,z)
   
   # toggle folding of node branch
   node.folded = ! node.folded?     # same as:    node.setFolded( ! node.isFolded() )


   # Description:
   #     Lambda that receives a target_node
   #     and returns an array of all ancestor nodes (including the root-node) 
   # Arguments: 
   #     target_node   - a Proxy.Node 
   # Returns:
   #     Array of nodes, where array[0] is root-node, and array[-1] is the target_node
   #    [root-node, ..., parent-of-parent-of-target_node, parent-of-target_node, target_node]
   # Usage:
   #    the_ancestry_of_node  = get_ancestry_array.call(node)
   #    the_ancestry_of_node.map {|n| n.text }
   #
   #    the_ancestry_of_nodeX = get_ancestry_array.call(nodeX)
   #    the_ancestry_of_nodeX.map {|n| n.text }
   #
   get_ancestry_array = lambda do |target_node| 
     ancestry = [target_node]
     ancestry.unshift(ancestry[0].parentNode) while ( not ancestry[0].root? )
     ancestry
   end
   the_ancestry_of_node  = get_ancestry_array.call(node)
   the_ancestry_of_node.map {|n| n.text }



   # Convert a java.util.List --> ruby Array
   my_java_list = node.findAll
   my_ruby_array = my_java_list.to_a


   # Show the Java-class of an object
   node.java_class
   #=> class org.freeplane.plugin.script.proxy.NodeProxy




If/when you want to understand more about this topic, then:

  • have a look into the Jruby wiki page dedicated to Calling Java from JRuby
  • get this book Using JRuby - The pragmatic programmers
    • read only "Chapter2 Driving Java from Ruby" and "Appendix B: Ruby/Java Interoperability"
    • you just need to read those 2 sections of the book, its a 2-3h read, not much
    • dont stress out - just read it once to get an idea, and get back to it if/when you latter need it.
    • NOTE: if you are a java programmer getting into jruby, this book may be it


  1. call java field






==

TODO

Ideas:

1: nodnode
2: node
3: node.text
4: invokeAndWait { node.createChild("Hi im child")
5: }
6: invokeAndWait { node.createChild("Hi im another child") }
7: invokeAndWait { node.text
8: }
9: node.text

10: invokeAndWait { node.text += "--> Added into the end of text <--" } 11: 3.times.do { |i| invokeAndWait { node.createChild("Hi im grandson #{i}") } } 12: 3.times.each { |i| invokeAndWait { node.createChild("Hi im grandson #{i}") } } 13: node.fold 14: node.folded? 15: node.folded = !node.folded? 16: node.folded = !node.folded? 17: invokeAndWait { node.folded = !node.folded? } 18: hist 19: hist --replay 3..12


NOTES: After editing an existing script, you dont need to restart freeplane. That is, you can just edit the ruby script, save it, and then in freelpane rerun the script, without restarting freeplane!

invokeAndWait {}

  • does not return a value from the block (always returns nil)
   [12] pry(main)> invokeAndWait { 1 }
   => nil
   [13] pry(main)> invokeAndWait { 5 }
   => nil
   [14] pry(main)> invokeAndWait { true }
   => nil
   [15] pry(main)> invokeAndWait { "invokeAndWait always returns nil, no matter what happens inside the block" }
   => nil
   [16] pry(main)> invokeAndWait { raise "Exceptions that happen inside the invokeAndWait block are discarded, but get registered into the freeplane log file" }
   => nil


  • ruby exceptions that happen inside the block are not shown in the debug window, but they are registered in freeplane log file
   [5] pry(main)> invokeAndWait { node.thisMethodDoesNotExist }
   => nil
   
   [16] pry(main)> invokeAndWait { raise "Exceptions that happen inside the invokeAndWait block are discarded, but get registered into the freeplane log file" }
   => nil


   # exception is registered in freeplane logs: <freeplane_user_dir>/logs/log.0
   STDERR: Exception in thread "AWT-EventQueue-0" 
   STDERR: org.jruby.exceptions.RaiseException: (NoMethodError) undefined method `thisMethodDoesNotExist' for #<Java::OrgFreeplanePluginScriptProxy::NodeProxy:0xdbe721>
   STDERR: 	at RUBY.block in evaluate_ruby((pry):6)
   STDERR: Exception in thread "AWT-EventQueue-0" 
   STDERR: org.jruby.exceptions.RaiseException: (RuntimeError) Exceptions that happen inside the invokeAndWait block are discarded, but get registered into the freeplane log file
   STDERR: 	at RUBY.block in evaluate_ruby((pry):18)
   


FreeplaneJrubyInstaller Addon - The making of

If someday you want to go into the rabbit hole of how this addon was made, understanding the details about the addon plugin and how this all came together into freeplane, then read the FreeplaneJrubyInstaller - making of.

Its a cmopletely optional and big reading - if your starting with scripting, leave it for a latter day :)