Repository: incubator-fluo-website
Updated Branches:
  refs/heads/gh-pages e0d01dc3d -> 31abb2c21


Update excercise 1 to add CFM and instructions on running in live instance.


Project: http://git-wip-us.apache.org/repos/asf/incubator-fluo-website/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-fluo-website/commit/31abb2c2
Tree: 
http://git-wip-us.apache.org/repos/asf/incubator-fluo-website/tree/31abb2c2
Diff: 
http://git-wip-us.apache.org/repos/asf/incubator-fluo-website/diff/31abb2c2

Branch: refs/heads/gh-pages
Commit: 31abb2c210c9050dbedd3383accfc686675e4f4c
Parents: e0d01dc
Author: Keith Turner <ktur...@apache.org>
Authored: Mon Nov 28 18:27:52 2016 -0500
Committer: Keith Turner <ktur...@apache.org>
Committed: Tue Nov 29 19:20:27 2016 -0500

----------------------------------------------------------------------
 tour/exercise-1.md | 544 ++++++++++++++++++++++++++++++++++--------------
 1 file changed, 392 insertions(+), 152 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-fluo-website/blob/31abb2c2/tour/exercise-1.md
----------------------------------------------------------------------
diff --git a/tour/exercise-1.md b/tour/exercise-1.md
index ff16126..39d28f7 100644
--- a/tour/exercise-1.md
+++ b/tour/exercise-1.md
@@ -1,5 +1,5 @@
 ---
-title: Word count Exercise
+title: Word counts for unique documents exercise
 ---
 
 This exercise gives you an opportunity to use everything you have learned so
@@ -138,11 +138,8 @@ Add the following to the ft.Main class.
     //line.
     mini.waitForObservers();
 
-    System.out.println("**** begin table dump ****");
-    try (Snapshot snap = client.newSnapshot()) {
-      snap.scanner().build().forEach(rcv -> System.out.println("  " + rcv));
-    }
-    System.out.println("**** end table dump ****\n");
+    FluoITHelper.printFluoTable(client);
+    System.out.println();
   }
 
   private static void excercise(MiniFluo mini, FluoClient client) {
@@ -155,46 +152,46 @@ Add the following to the ft.Main class.
 Once the TODOs in the DocLoader class are implemented, running Main should 
print out the following.
 
 ```
-**** begin table dump ****
-  d:a6c4d1f doc content  Jebediah orbits Mun for 35 days.  No power, forgot 
solar panels.
-  d:a6c4d1f doc refc  1
-  d:a6c4d1f doc refs  referenced
-  d:cf8ddc0 doc content  Bill plans to rescue Jebediah after taking tourist to 
Minimus.
-  d:cf8ddc0 doc refc  1
-  d:cf8ddc0 doc refs  referenced
-  u:http://news.com/a23 uri hash  a6c4d1f
-  u:http://news.com/a24 uri hash  cf8ddc0
-**** end table dump ****
-
-**** begin table dump ****
-  d:a6c4d1f doc content  Jebediah orbits Mun for 35 days.  No power, forgot 
solar panels.
-  d:a6c4d1f doc refc  2
-  d:a6c4d1f doc refs  referenced
-  d:cf8ddc0 doc content  Bill plans to rescue Jebediah after taking tourist to 
Minimus.
-  d:cf8ddc0 doc refc  1
-  d:cf8ddc0 doc refs  referenced
-  u:http://news.com/a23 uri hash  a6c4d1f
-  u:http://news.com/a24 uri hash  cf8ddc0
-  u:http://oldnews.com/a23 uri hash  a6c4d1f
-**** end table dump ****
-
-**** begin table dump ****
-  d:2732ebc doc content  Crisis at KSC.  Tourist stuck at Minimus.  Bill 
forgot solar panels.
-  d:2732ebc doc refc  1
-  d:2732ebc doc refs  referenced
-  d:6658252 doc content  Jebediah orbits Mun for 38 days.  No power, forgot 
solar panels.
-  d:6658252 doc refc  1
-  d:6658252 doc refs  referenced
-  d:a6c4d1f doc content  Jebediah orbits Mun for 35 days.  No power, forgot 
solar panels.
-  d:a6c4d1f doc refc  1
-  d:a6c4d1f doc refs  referenced
-  d:cf8ddc0 doc content  Bill plans to rescue Jebediah after taking tourist to 
Minimus.
-  d:cf8ddc0 doc refc  0
-  d:cf8ddc0 doc refs  unreferenced
-  u:http://news.com/a23 uri hash  6658252
-  u:http://news.com/a24 uri hash  2732ebc
-  u:http://oldnews.com/a23 uri hash  a6c4d1f
-**** end table dump ****
+== fluo start ==
+d:a6c4d1f doc content  Jebediah orbits Mun for 35 days.  No power, forgot 
solar panels.
+d:a6c4d1f doc refc     1
+d:a6c4d1f doc refs     referenced
+d:cf8ddc0 doc content  Bill plans to rescue Jebediah after taking tourist to 
Minimus.
+d:cf8ddc0 doc refc     1
+d:cf8ddc0 doc refs     referenced
+u:http://news.com/a23 uri hash a6c4d1f
+u:http://news.com/a24 uri hash cf8ddc0
+=== fluo end ===
+
+== fluo start ==
+d:a6c4d1f doc content  Jebediah orbits Mun for 35 days.  No power, forgot 
solar panels.
+d:a6c4d1f doc refc     2
+d:a6c4d1f doc refs     referenced
+d:cf8ddc0 doc content  Bill plans to rescue Jebediah after taking tourist to 
Minimus.
+d:cf8ddc0 doc refc     1
+d:cf8ddc0 doc refs     referenced
+u:http://news.com/a23 uri hash a6c4d1f
+u:http://news.com/a24 uri hash cf8ddc0
+u:http://oldnews.com/a23 uri hash      a6c4d1f
+=== fluo end ===
+
+== fluo start ==
+d:2732ebc doc content  Crisis at KSC.  Tourist stuck at Minimus.  Bill forgot 
solar panels.
+d:2732ebc doc refc     1
+d:2732ebc doc refs     referenced
+d:6658252 doc content  Jebediah orbits Mun for 38 days.  No power, forgot 
solar panels.
+d:6658252 doc refc     1
+d:6658252 doc refs     referenced
+d:a6c4d1f doc content  Jebediah orbits Mun for 35 days.  No power, forgot 
solar panels.
+d:a6c4d1f doc refc     1
+d:a6c4d1f doc refs     referenced
+d:cf8ddc0 doc content  Bill plans to rescue Jebediah after taking tourist to 
Minimus.
+d:cf8ddc0 doc refc     0
+d:cf8ddc0 doc refs     unreferenced
+u:http://news.com/a23 uri hash 6658252
+u:http://news.com/a24 uri hash 2732ebc
+u:http://oldnews.com/a23 uri hash      a6c4d1f
+=== fluo end ===
 ```
 
 ## Part 2 : Computing word counts.
@@ -240,14 +237,15 @@ public class ContentObserver extends AbstractObserver {
   /**
    * Utility method to tokenize the content of a document into unique words.
    */
-  private Set<String> tokenize(String content) {
-    return new HashSet<String>(Arrays.asList(content.split("[ .!,]+")));
+  private List<String> tokenize(String content) {
+    return Arrays.asList(content.split("[\\W]+"));
   }
 
   /**
    *  Adds the passed to delta to the values for each word.
    */
-  private void adjustCounts(TransactionBase tx, int delta, Set<String> words) {
+  private void adjustCounts(TransactionBase tx, int delta, List<String> words) 
{
+     Set<String> uniqueWords = new HashSet<String>(words);
     // TODO make a single call to get all of the current word counts.  Could 
use
     //tx.gets(Collection<RowColumn>)
 
@@ -297,113 +295,355 @@ observer as follows.
 After implementing the Observer, the output of the program should look like 
the following.
 
 ```
-**** begin table dump ****
-  d:a6c4d1f doc content  Jebediah orbits Mun for 35 days.  No power, forgot 
solar panels.
-  d:a6c4d1f doc processed  true
-  d:a6c4d1f doc refc  1
-  d:a6c4d1f doc refs  referenced
-  d:cf8ddc0 doc content  Bill plans to rescue Jebediah after taking tourist to 
Minimus.
-  d:cf8ddc0 doc processed  true
-  d:cf8ddc0 doc refc  1
-  d:cf8ddc0 doc refs  referenced
-  u:http://news.com/a23 uri hash  a6c4d1f
-  u:http://news.com/a24 uri hash  cf8ddc0
-  w:35 word docCount  1
-  w:Bill word docCount  1
-  w:Jebediah word docCount  2
-  w:Minimus word docCount  1
-  w:Mun word docCount  1
-  w:No word docCount  1
-  w:after word docCount  1
-  w:days word docCount  1
-  w:for word docCount  1
-  w:forgot word docCount  1
-  w:orbits word docCount  1
-  w:panels word docCount  1
-  w:plans word docCount  1
-  w:power word docCount  1
-  w:rescue word docCount  1
-  w:solar word docCount  1
-  w:taking word docCount  1
-  w:to word docCount  1
-  w:tourist word docCount  1
-**** end table dump ****
-
-**** begin table dump ****
-  d:a6c4d1f doc content  Jebediah orbits Mun for 35 days.  No power, forgot 
solar panels.
-  d:a6c4d1f doc processed  true
-  d:a6c4d1f doc refc  2
-  d:a6c4d1f doc refs  referenced
-  d:cf8ddc0 doc content  Bill plans to rescue Jebediah after taking tourist to 
Minimus.
-  d:cf8ddc0 doc processed  true
-  d:cf8ddc0 doc refc  1
-  d:cf8ddc0 doc refs  referenced
-  u:http://news.com/a23 uri hash  a6c4d1f
-  u:http://news.com/a24 uri hash  cf8ddc0
-  u:http://oldnews.com/a23 uri hash  a6c4d1f
-  w:35 word docCount  1
-  w:Bill word docCount  1
-  w:Jebediah word docCount  2
-  w:Minimus word docCount  1
-  w:Mun word docCount  1
-  w:No word docCount  1
-  w:after word docCount  1
-  w:days word docCount  1
-  w:for word docCount  1
-  w:forgot word docCount  1
-  w:orbits word docCount  1
-  w:panels word docCount  1
-  w:plans word docCount  1
-  w:power word docCount  1
-  w:rescue word docCount  1
-  w:solar word docCount  1
-  w:taking word docCount  1
-  w:to word docCount  1
-  w:tourist word docCount  1
-**** end table dump ****
-
-**** begin table dump ****
-  d:2732ebc doc content  Crisis at KSC.  Tourist stuck at Minimus.  Bill 
forgot solar panels.
-  d:2732ebc doc processed  true
-  d:2732ebc doc refc  1
-  d:2732ebc doc refs  referenced
-  d:6658252 doc content  Jebediah orbits Mun for 38 days.  No power, forgot 
solar panels.
-  d:6658252 doc processed  true
-  d:6658252 doc refc  1
-  d:6658252 doc refs  referenced
-  d:a6c4d1f doc content  Jebediah orbits Mun for 35 days.  No power, forgot 
solar panels.
-  d:a6c4d1f doc processed  true
-  d:a6c4d1f doc refc  1
-  d:a6c4d1f doc refs  referenced
-  u:http://news.com/a23 uri hash  6658252
-  u:http://news.com/a24 uri hash  2732ebc
-  u:http://oldnews.com/a23 uri hash  a6c4d1f
-  w:35 word docCount  1
-  w:38 word docCount  1
-  w:Bill word docCount  1
-  w:Crisis word docCount  1
-  w:Jebediah word docCount  2
-  w:KSC word docCount  1
-  w:Minimus word docCount  1
-  w:Mun word docCount  2
-  w:No word docCount  2
-  w:Tourist word docCount  1
-  w:at word docCount  1
-  w:days word docCount  2
-  w:for word docCount  2
-  w:forgot word docCount  3
-  w:orbits word docCount  2
-  w:panels word docCount  3
-  w:power word docCount  2
-  w:solar word docCount  3
-  w:stuck word docCount  1
-**** end table dump ****
+== fluo start ==
+d:a6c4d1f doc content  Jebediah orbits Mun for 35 days.  No power, forgot 
solar panels.
+d:a6c4d1f doc processed        true
+d:a6c4d1f doc refc     1
+d:a6c4d1f doc refs     referenced
+d:cf8ddc0 doc content  Bill plans to rescue Jebediah after taking tourist to 
Minimus.
+d:cf8ddc0 doc processed        true
+d:cf8ddc0 doc refc     1
+d:cf8ddc0 doc refs     referenced
+u:http://news.com/a23 uri hash a6c4d1f
+u:http://news.com/a24 uri hash cf8ddc0
+w:35 word docCount     1
+w:Bill word docCount   1
+w:Jebediah word docCount       2
+w:Minimus word docCount        1
+w:Mun word docCount    1
+w:No word docCount     1
+w:after word docCount  1
+w:days word docCount   1
+w:for word docCount    1
+w:forgot word docCount 1
+w:orbits word docCount 1
+w:panels word docCount 1
+w:plans word docCount  1
+w:power word docCount  1
+w:rescue word docCount 1
+w:solar word docCount  1
+w:taking word docCount 1
+w:to word docCount     1
+w:tourist word docCount        1
+=== fluo end ===
+
+== fluo start ==
+d:a6c4d1f doc content  Jebediah orbits Mun for 35 days.  No power, forgot 
solar panels.
+d:a6c4d1f doc processed        true
+d:a6c4d1f doc refc     2
+d:a6c4d1f doc refs     referenced
+d:cf8ddc0 doc content  Bill plans to rescue Jebediah after taking tourist to 
Minimus.
+d:cf8ddc0 doc processed        true
+d:cf8ddc0 doc refc     1
+d:cf8ddc0 doc refs     referenced
+u:http://news.com/a23 uri hash a6c4d1f
+u:http://news.com/a24 uri hash cf8ddc0
+u:http://oldnews.com/a23 uri hash      a6c4d1f
+w:35 word docCount     1
+w:Bill word docCount   1
+w:Jebediah word docCount       2
+w:Minimus word docCount        1
+w:Mun word docCount    1
+w:No word docCount     1
+w:after word docCount  1
+w:days word docCount   1
+w:for word docCount    1
+w:forgot word docCount 1
+w:orbits word docCount 1
+w:panels word docCount 1
+w:plans word docCount  1
+w:power word docCount  1
+w:rescue word docCount 1
+w:solar word docCount  1
+w:taking word docCount 1
+w:to word docCount     1
+w:tourist word docCount        1
+=== fluo end ===
+
+== fluo start ==
+d:2732ebc doc content  Crisis at KSC.  Tourist stuck at Minimus.  Bill forgot 
solar panels.
+d:2732ebc doc processed        true
+d:2732ebc doc refc     1
+d:2732ebc doc refs     referenced
+d:6658252 doc content  Jebediah orbits Mun for 38 days.  No power, forgot 
solar panels.
+d:6658252 doc processed        true
+d:6658252 doc refc     1
+d:6658252 doc refs     referenced
+d:a6c4d1f doc content  Jebediah orbits Mun for 35 days.  No power, forgot 
solar panels.
+d:a6c4d1f doc processed        true
+d:a6c4d1f doc refc     1
+d:a6c4d1f doc refs     referenced
+u:http://news.com/a23 uri hash 6658252
+u:http://news.com/a24 uri hash 2732ebc
+u:http://oldnews.com/a23 uri hash      a6c4d1f
+w:35 word docCount     1
+w:38 word docCount     1
+w:Bill word docCount   1
+w:Crisis word docCount 1
+w:Jebediah word docCount       2
+w:KSC word docCount    1
+w:Minimus word docCount        1
+w:Mun word docCount    2
+w:No word docCount     2
+w:Tourist word docCount        1
+w:at word docCount     1
+w:days word docCount   2
+w:for word docCount    2
+w:forgot word docCount 3
+w:orbits word docCount 2
+w:panels word docCount 3
+w:power word docCount  2
+w:solar word docCount  3
+w:stuck word docCount  1
+=== fluo end ===
 ```
 
 ## Part 3 : Using Fluo Recipes
 
 The way to compute word counts above is very prone to transactional 
collisions. One way to avoid
-these collisions is to use the CollisionFreeMap provided in Fluo Recipes. 
Currently Fluo Recipes is
-not released, this section will be updated with more information once it is.
+these collisions is to use the CollisionFreeMap(CFM) provided in Fluo Recipes. 
 The CFM will queue
+updates for words and notify another observer to process the queued updates.  
The updates are queued
+in a way that will not cause collisions.  The CFM has its own Observer which 
will call two functions
+you provide.  The code below shows an example of these two functions and how 
to configure the CFM to
+call them.
+
+To try using a CFM, first add the following class.
+
+```java
+package ft;
+
+import java.util.*;
+
+import org.apache.fluo.api.client.TransactionBase;
+import org.apache.fluo.api.config.FluoConfiguration;
+import org.apache.fluo.api.config.SimpleConfiguration;
+import org.apache.fluo.recipes.core.map.CollisionFreeMap;
+import org.apache.fluo.recipes.core.map.Combiner;
+import org.apache.fluo.recipes.core.map.Update;
+import org.apache.fluo.recipes.core.map.UpdateObserver;
+
+import static ft.ContentObserver.WORD_COUNT;
+
+/**
+ * This class contains all of the code related to the {@link CollisionFreeMap} 
that keeps track of
+ * word counts.  It also generates an inverted index of word counts as an 
example follow on action.
+ */
+public class WordCounter {
+
+  /**
+   * the {@link CollisionFreeMap} Observer calls this combiner to processes 
the queued updates for
+   * a word.
+   */
+  public static class LongCombiner implements Combiner<String, Long>{
+
+    @Override
+    public Optional<Long> combine(String k, Iterator<Long> counts) {
+      long sum = 0;
+      while(counts.hasNext()) {
+        sum += counts.next();
+      }
+
+      if(sum == 0) {
+        return Optional.empty();
+      } else {
+        return Optional.of(sum);
+      }
+    }
+  }
+
+  /**
+   * The {@link CollisionFreeMap} Observer will call this class when the 
counts for a word change.
+   */
+  public static class WordObserver extends UpdateObserver<String, Long> {
+    @Override
+    public void updatingValues(TransactionBase tx, Iterator<Update<String, 
Long>> updates) {
+      System.out.println("== begin CFM updates ==");  //this print to show per 
bucket processing
+      while(updates.hasNext()) {
+        Update<String, Long> u = updates.next();
+
+        long oldCount = u.getOldValue().orElse(0l);
+        long newCount = u.getNewValue().orElse(0l);
+
+        //create an inverted index of word counts
+        if(u.getOldValue().isPresent()) {
+          tx.delete(String.format("ic:%06d:%s", oldCount, u.getKey()), 
WORD_COUNT);
+        }
+
+        if(u.getNewValue().isPresent()) {
+          tx.set(String.format("ic:%06d:%s", newCount, u.getKey()), 
WORD_COUNT, "");
+        }
+
+        System.out.printf("  update %s %d -> %d\n", u.getKey(), oldCount , 
newCount);
+      }
+      System.out.println("== end CFM updates ==");
+    }
+  }
+
+  /**
+   * Code to setup a CFM's Observer and configure it to call your functions.
+   */
+  public static void configure(FluoConfiguration fluoConfig, int numBuckets) {
+    CollisionFreeMap.configure(fluoConfig, new CollisionFreeMap.Options("wc", 
LongCombiner.class,
+        WordObserver.class, String.class, Long.class, 3));
+  }
+
+  private CollisionFreeMap<String, Long> cfm;
+
+  WordCounter(SimpleConfiguration appConfig){
+    cfm = CollisionFreeMap.getInstance("wc", appConfig);
+  }
+
+  /**
+   * This method will queue updates for each word to be processed later by the 
CFM Observer.
+   */
+  void adjustCounts(TransactionBase tx, int delta, List<String> words){
+    HashMap<String, Long> wcUpdates = new HashMap<>();
+
+    for (String word : words) {
+      wcUpdates.put(word, (long)delta);
+    }
+
+    cfm.update(tx, wcUpdates);
+  }
+}
+```
+
+Then modify `preInit()` in `Main` to the following.
+
+```java
+  private static void preInit(FluoConfiguration fluoConfig) {
+    fluoConfig.addObserver(new 
ObserverSpecification(ContentObserver.class.getName()));
+    WordCounter.configure(fluoConfig, 3);
+  }
+```
+
+After that add the following `init()` method to `ContentObserver` and modify 
`adjustCounts()` to the
+following.
+
+```java
+  private WordCounter wordCounter;
+
+  @Override
+  public void init(Context context) throws Exception {
+    //get an instance of the CFM based on application config
+    wordCounter = new WordCounter(context.getAppConfiguration());
+  }
+
+  private void adjustCounts(TransactionBase tx, int delta, String[] words) {
+    wordCounter.adjustCounts(tx, delta, words);
+  }
+```
+
+The CFM groups key values into buckets for efficiency and processes the 
updates for entire bucket in
+a single transaction.  When you run this new code, that is why `== begin CFM 
updates ==` is seen at
+least three times for each group of documents loaded.
+
+When you run this example you will also notice two new prefixes in the output 
of the table scan.  First
+the `wc:` prefix is where the CFM stores its data.  By default the CFM uses 
Kryo for serialization
+and therefore the key values with this prefix contain non-ASCII characters.  
The utility function
+`FluoITHelper.printFluoTable()` escapes non-ASCII characters with `\x<HEX>`.   
Second the `ic:`
+prefix contains an inverted index of word counts.  This was created simply to 
show an example of a
+follow on action when word counts change.  Ideally this follow on action would 
have a low chance of
+collisions.  Creating the inverted index will not cause collisions because 
each word is in a single
+CFM bucket and each bucket is processed independently.
+
+## Part 4 : Running this example on a real instance.
+
+Everything in the tour so far has used MiniFluo to run code.  The following
+instructions show how to run the code in this excercise on a real Fluo
+instance.  [Uno] can be used to quickly setup Fluo on a single node.
+
+The following two helper classes will be needed to run on a real instance.
+
+```java
+package ft;
+
+import org.apache.fluo.api.config.FluoConfiguration;
+
+/**
+ * Generates application config.
+ */
+public class GenConfig {
+  public static void main(String[] args) {
+    FluoConfiguration conf = new FluoConfiguration();
+    Main.preInit(conf);
+    conf.save(System.out);
+  }
+}
+```
+
+
+```java
+package ft;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.*;
+import javax.inject.Inject;
+import org.apache.fluo.api.client.*;
+import org.apache.fluo.api.config.FluoConfiguration;
+
+/**
+ * Loads one or more document passed in on the command line.
+ */
+public class Load {
+  // when run with fluo exec command, the applications configuration will be 
injected
+  @Inject
+  private static FluoConfiguration fluoConfig;
+
+  public static void main(String[] args) throws Exception {
+    try (FluoClient client = FluoFactory.newClient(fluoConfig);
+        LoaderExecutor loaderExecutor = client.newLoaderExecutor()) 
+    {
+      for (String filename : args) {
+        Path path = Paths.get(filename);
+        byte[] encoded = Files.readAllBytes(path);
+        String docContent = new String(encoded, StandardCharsets.UTF_8);
+        String uri = path.toAbsolutePath().normalize().toUri().toString();
+        Document doc = new Document(uri, docContent);
+        loaderExecutor.execute(new DocLoader(doc));
+      }
+    }
+  }
+}
+```
+
+The following command will run this application on a Fluo instance, assuming
+`$FLUO_HOME` is set and `fluo` is on the path.
+
+```bash
+cd <your fluo tour dir>
+
+#create a new Fluo application named wordCount
+fluo new wordCount
+
+#populate applications lib directory
+mvn clean package
+cp target/fluo-tour-0.0.1-SNAPSHOT.jar $FLUO_HOME/apps/wordCount/lib
+mvn dependency:copy-dependencies 
-DincludeArtifactIds="fluo-recipes-core,fluo-recipes-accumulo,fluo-recipes-kryo,kryo,minlog,reflectasm,objenesis"
 -DoutputDirectory=$FLUO_HOME/apps/wordCount/lib
+
+#add app specific config to properties file that Fluo init will use
+fluo exec wordCount ft.GenConfig >> 
$FLUO_HOME/apps/wordCount/conf/fluo.properties
+
+#initialize and start Fluo application
+fluo init wordCount
+fluo start wordCount
+fluo info wordCount
+
+#load some text files
+fluo exec wordCount ft.Load <some filename> <some filename> ...
+
+#wait for all notifications to process
+fluo wait wordCount
+
+#scan data in Fluo
+fluo scan wordCount
+fluo scan wordCount -p ic:
+
+#Could try changing a file and reloading it
+
+#stop Fluo application
+fluo stop wordCount
+```
 
 [aft]: https://twitter.com/hashtag/apachefluotour
+[Uno]: https://github.com/astralway/uno

Reply via email to